# RR SHOP 22FEB26 LAST WORKING CODES_NO TCPDF AND UPLOADS CONTENT - REPOSITORY ================================================================================ Project Name: RR shop 22Feb26 Last Working Codes_no tcpdf and uploads content Created: 2026-02-22 16:41:08 Last Updated: 2026-02-22 16:41:16 Source ZIP: Relevant Reflex Shop (2).zip Total Files: 44 Total Folders: 9 ================================================================================ ## FILE STRUCTURE ================================================================================ RR shop 22Feb26 Last Working Codes_no tcpdf and uploads content/ └── Relevant Reflex Shop/ ├── admin-invoice-pdf.php ├── affiliate_create.php ├── affiliate_edit.php ├── affiliate_view.php ├── assets/ │ ├── css/ │ │ ├── dashboard.css │ │ ├── main.css │ │ ├── member-enhancements.css │ │ └── responsive.css │ ├── images/ │ │ └── logo.svg │ └── js/ │ ├── dashboard.js │ └── main.js ├── clients.php ├── client_create.php ├── client_edit.php ├── client_view.php ├── config.php ├── demand.php ├── error-404.html ├── error-500.html ├── finance.php ├── generate_hash.php ├── generate_panelbook.py ├── includes/ │ ├── footer.php │ ├── header.php │ └── navigation.php ├── index.php ├── letterhead-download.php ├── login.php ├── logout.php ├── maintenance.html ├── members.php ├── member_edit.php ├── member_view.php ├── pan-action.php ├── panel.php ├── panelbook-generate.php ├── README.md ├── robots.txt ├── settings.php ├── sitemap.xml ├── superlog-generate.php ├── supply.php ├── support.php ├── tcpdf ├── uploads/ │ └── clients └── users.php ================================================================================ ## FILE CONTENTS ================================================================================ ### FILE 1: Relevant Reflex Shop/admin-invoice-pdf.php - Type: PHP - Size: 13.48 KB - Path: Relevant Reflex Shop - Name: admin-invoice-pdf.php ------------------------------------------------------------ prepare(" SELECT i.*, p.project_name, p.eloi, p.sample_size, p.industry, p.closed_at FROM invoices i INNER JOIN projects p ON i.project_id = p.id WHERE i.id = ? "); $stmt->execute([$invoiceId]); $inv = $stmt->fetch(PDO::FETCH_ASSOC); if (!$inv) { header('Location: finance.php'); exit; } // Load company settings for bank details & footer $settings = []; try { $result = $pdo->query("SELECT setting_key, setting_value FROM company_settings"); while ($row = $result->fetch()) $settings[$row['setting_key']] = $row['setting_value']; } catch (Exception $e) {} $sym = $inv['currency_symbol'] ?? '₹'; $currLabel = ['INR'=>'Indian Rupees','USD'=>'US Dollars','EUR'=>'Euros'][$inv['currency']] ?? $inv['currency']; ?> Invoice <?php echo htmlspecialchars($inv['invoice_number']); ?>
← Back to Finance

🌐

INVOICE

GSTIN:
PAN:
SAC:

Bill To

Project Details


Industry:
Invoice Date
Due Date (Net 30)
Currency
#DescriptionQtyRateAmount
1 Sample Cost
Online survey fielding & data collection (Avg LOI: min)
completes
2 Respondent Incentive
Panel member incentive payments
completes
Sample Cost
Incentive Cost
Total Amount
* Inclusive of all applicable taxes
✅ PAID on — Ref:

Bank Details for Payment

'Bank Name','bank_account_name'=>'Account Name','bank_account_number'=>'Account Number','bank_ifsc'=>'IFSC Code','bank_branch'=>'Branch','bank_swift'=>'SWIFT Code'] as $k=>$l): ?>

Terms & Conditions

Notes

-------------------- END OF FILE -------------------- ### FILE 2: Relevant Reflex Shop/affiliate_create.php - Type: PHP - Size: 17.14 KB - Path: Relevant Reflex Shop - Name: affiliate_create.php ------------------------------------------------------------ ------------------------------------------------------------ prepare("SELECT id FROM affiliates WHERE affiliate_code = ?"); $checkStmt->execute([$affiliate_code]); } while ($checkStmt->fetch()); // Insert affiliate $stmt = $pdo->prepare(" INSERT INTO affiliates (affiliate_code, type, company_name, incharge_name, state, postal_code, place_name, mobile, email, url, signup_reward, survey_reward, created_by, status, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NOW()) "); $stmt->execute([ $affiliate_code, $type, $company_name, $incharge_name, $state, $postal_code, $place_name, $mobile, $email, $url, $signup_reward, $survey_reward, $_SESSION['admin_id'] ]); $affiliate_id = $pdo->lastInsertId(); // Handle file uploads if (!empty($_FILES['attachments']['name'][0])) { $upload_dir = 'uploads/affiliates/'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0755, true); } $allowed_types = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png', 'image/gif']; $max_files = 10; $max_size = 5 * 1024 * 1024; // 5MB for ($i = 0; $i < min(count($_FILES['attachments']['name']), $max_files); $i++) { if ($_FILES['attachments']['error'][$i] === UPLOAD_ERR_OK) { $file_type = $_FILES['attachments']['type'][$i]; $file_size = $_FILES['attachments']['size'][$i]; if (in_array($file_type, $allowed_types) && $file_size <= $max_size) { $file_name = $_FILES['attachments']['name'][$i]; $file_tmp = $_FILES['attachments']['tmp_name'][$i]; // Generate unique filename $extension = pathinfo($file_name, PATHINFO_EXTENSION); $unique_name = $affiliate_code . '_' . time() . '_' . $i . '.' . $extension; $file_path = $upload_dir . $unique_name; if (move_uploaded_file($file_tmp, $file_path)) { $attachStmt = $pdo->prepare(" INSERT INTO affiliate_attachments (affiliate_id, file_name, file_path, file_type, file_size, uploaded_at) VALUES (?, ?, ?, ?, ?, NOW()) "); $attachStmt->execute([ $affiliate_id, $file_name, $file_path, $file_type, $file_size ]); } } } } } logActivity($_SESSION['admin_id'], 'create_affiliate', "Created affiliate: $affiliate_code", 'affiliate', $affiliate_id); header('Location: supply.php?success=created&code=' . $affiliate_code); exit; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Create affiliate error: " . $e->getMessage()); } } } include 'includes/header.php'; ?>
₹5 for email verification + rest for mobile verification
Per survey completed by referred members

📑 Click to upload or drag and drop files here

PDF, JPG, PNG, GIF (Max 10 files, 5MB each) Upload GST Certificate, PAN Card, Cancelled Cheque, Business Registration, etc.
Cancel
-------------------- END OF FILE -------------------- ### FILE 3: Relevant Reflex Shop/affiliate_edit.php - Type: PHP - Size: 13.21 KB - Path: Relevant Reflex Shop - Name: affiliate_edit.php ------------------------------------------------------------ prepare(" UPDATE affiliates SET type = ?, company_name = ?, incharge_name = ?, state = ?, postal_code = ?, place_name = ?, mobile = ?, email = ?, url = ?, signup_reward = ?, survey_reward = ?, status = ?, updated_at = NOW() WHERE id = ? "); $stmt->execute([ $type, $company_name, $incharge_name, $state, $postal_code, $place_name, $mobile, $email, $url, $signup_reward, $survey_reward, $status, $affiliate_id ]); logActivity($_SESSION['admin_id'], 'update_affiliate', "Updated affiliate #$affiliate_id", 'affiliate', $affiliate_id); $success = 'Affiliate updated successfully!'; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Update affiliate error: " . $e->getMessage()); } } } } // Fetch affiliate details try { $pdo = getDBConnection(); $stmt = $pdo->prepare("SELECT * FROM affiliates WHERE id = ?"); $stmt->execute([$affiliate_id]); $affiliate = $stmt->fetch(); if (!$affiliate) { header('Location: supply.php'); exit; } } catch (Exception $e) { error_log("Fetch affiliate error: " . $e->getMessage()); header('Location: supply.php'); exit; } $indian_states = [ 'Andhra Pradesh', 'Arunachal Pradesh', 'Assam', 'Bihar', 'Chhattisgarh', 'Goa', 'Gujarat', 'Haryana', 'Himachal Pradesh', 'Jharkhand', 'Karnataka', 'Kerala', 'Madhya Pradesh', 'Maharashtra', 'Manipur', 'Meghalaya', 'Mizoram', 'Nagaland', 'Odisha', 'Punjab', 'Rajasthan', 'Sikkim', 'Tamil Nadu', 'Telangana', 'Tripura', 'Uttar Pradesh', 'Uttarakhand', 'West Bengal', 'Andaman and Nicobar Islands', 'Chandigarh', 'Dadra and Nagar Haveli and Daman and Diu', 'Delhi', 'Jammu and Kashmir', 'Ladakh', 'Lakshadweep', 'Puducherry' ]; include 'includes/header.php'; ?>
₹5 for email verification + rest for mobile verification
Per survey completed by referred members
-------------------- END OF FILE -------------------- ### FILE 4: Relevant Reflex Shop/affiliate_view.php - Type: PHP - Size: 14.53 KB - Path: Relevant Reflex Shop - Name: affiliate_view.php ------------------------------------------------------------ ------------------------------------------------------------ prepare("SELECT * FROM affiliates WHERE id = ?"); $stmt->execute([$affiliate_id]); $affiliate = $stmt->fetch(); if (!$affiliate) { header('Location: supply.php'); exit; } // Fetch attachments $attachStmt = $pdo->prepare("SELECT * FROM affiliate_attachments WHERE affiliate_id = ?"); $attachStmt->execute([$affiliate_id]); $attachments = $attachStmt->fetchAll(); // Fetch signups $signupsStmt = $pdo->prepare(" SELECT * FROM affiliate_signups WHERE affiliate_id = ? ORDER BY clicked_at DESC LIMIT 100 "); $signupsStmt->execute([$affiliate_id]); $signups = $signupsStmt->fetchAll(); } catch (Exception $e) { error_log("View affiliate error: " . $e->getMessage()); header('Location: supply.php'); exit; } $signup_url = 'https://relevantreflex.com/signup.php?ref=' . $affiliate['affiliate_code']; include 'includes/header.php'; ?>

Affiliate Code & Signup Link

Basic Information

Type
Company Name
In-charge Name
State
Location

PIN:
Mobile
Email
Website/URL
Signup Reward
₹5 email + ₹ mobile
Survey Revenue Share
Per survey completed by referred members
Status
Created

Performance Statistics

Total Hits
Not Verified
prepare("SELECT COUNT(*) FROM affiliate_signups WHERE affiliate_id = ? AND signup_completed = 1 AND email_verified = 0"); $nvStmt->execute([$affiliate['id']]); echo $nvStmt->fetchColumn(); } catch (Exception $e) { echo '0'; } ?>
Verified Signups
Total Rewards Paid
Conversion Rate
0 ? ($affiliate['total_verified_signups'] / $affiliate['total_signups'] * 100) : 0; echo number_format($conversion, 1); ?>%

Attachments ()


KB • Uploaded on
💥 Download

Recent Signups (Last 100)

No signups yet

Email Clicked At Signed Up Verified Reward Status
Not completed'; ?> Pending Not verified Pending Complete Awaiting Verification Clicked Only
-------------------- END OF FILE -------------------- ### FILE 5: Relevant Reflex Shop/client_create.php - Type: PHP - Size: 17.34 KB - Path: Relevant Reflex Shop - Name: client_create.php ------------------------------------------------------------ prepare("SELECT COUNT(*) FROM clients WHERE client_code = ?"); $stmt->execute([$client_code]); } while ($stmt->fetchColumn() > 0); $stmt = $pdo->prepare(" INSERT INTO clients (client_code, company_name, industry, contact_person, email, phone, address, city, country, postal_code, website, notes, currency, created_by, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) "); $stmt->execute([ $client_code, $company_name, $industry, $contact_person, $email, $phone, $address, $city, $country, $postal_code, $website, $notes, $currency, $_SESSION['admin_id'] ]); $client_id = $pdo->lastInsertId(); // Handle file uploads if (!empty($_FILES['attachments']['name'][0])) { $upload_dir = 'uploads/clients/'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0755, true); } $allowed_types = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png', 'image/gif']; $max_files = 10; $max_size = 5 * 1024 * 1024; // 5MB for ($i = 0; $i < min(count($_FILES['attachments']['name']), $max_files); $i++) { if ($_FILES['attachments']['error'][$i] === UPLOAD_ERR_OK) { $file_type = $_FILES['attachments']['type'][$i]; $file_size = $_FILES['attachments']['size'][$i]; if (in_array($file_type, $allowed_types) && $file_size <= $max_size) { $file_name = $_FILES['attachments']['name'][$i]; $file_tmp = $_FILES['attachments']['tmp_name'][$i]; $extension = pathinfo($file_name, PATHINFO_EXTENSION); $unique_name = $client_code . '_' . time() . '_' . $i . '.' . $extension; $file_path = $upload_dir . $unique_name; if (move_uploaded_file($file_tmp, $file_path)) { $attachStmt = $pdo->prepare(" INSERT INTO client_attachments (client_id, file_name, file_path, file_type, file_size, uploaded_at) VALUES (?, ?, ?, ?, ?, NOW()) "); $attachStmt->execute([ $client_id, $file_name, $file_path, $file_type, $file_size ]); } } } } } logActivity($_SESSION['admin_id'], 'create_client', "Created client: $client_code", 'client', $client_id); header('Location: clients.php?success=created&code=' . $client_code); exit; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Create client error: " . $e->getMessage()); } } } include 'includes/header.php'; ?>
Include country code (e.g., +91 for India, +1 for USA)

📑 Click to upload or drag and drop files here

PDF, JPG, PNG, GIF (Max 10 files, 5MB each) Upload contracts, agreements, proposals, GST certificate, etc.
Cancel
-------------------- END OF FILE -------------------- ### FILE 6: Relevant Reflex Shop/client_edit.php - Type: PHP - Size: 19.31 KB - Path: Relevant Reflex Shop - Name: client_edit.php ------------------------------------------------------------ prepare(" UPDATE clients SET company_name = ?, industry = ?, contact_person = ?, email = ?, phone = ?, address = ?, city = ?, country = ?, postal_code = ?, website = ?, notes = ?, status = ?, currency = ?, updated_at = NOW() WHERE id = ? "); $stmt->execute([ $company_name, $industry, $contact_person, $email, $phone, $address, $city, $country, $postal_code, $website, $notes, $status, $currency, $client_id ]); // Handle file uploads if (!empty($_FILES['attachments']['name'][0])) { $upload_dir = 'uploads/clients/'; if (!file_exists($upload_dir)) { mkdir($upload_dir, 0755, true); } $allowed_types = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png', 'image/gif']; $max_size = 5 * 1024 * 1024; // Get client code $clientStmt = $pdo->prepare("SELECT client_code FROM clients WHERE id = ?"); $clientStmt->execute([$client_id]); $client_code = $clientStmt->fetchColumn(); for ($i = 0; $i < count($_FILES['attachments']['name']); $i++) { if ($_FILES['attachments']['error'][$i] === UPLOAD_ERR_OK) { $file_type = $_FILES['attachments']['type'][$i]; $file_size = $_FILES['attachments']['size'][$i]; if (in_array($file_type, $allowed_types) && $file_size <= $max_size) { $file_name = $_FILES['attachments']['name'][$i]; $file_tmp = $_FILES['attachments']['tmp_name'][$i]; $extension = pathinfo($file_name, PATHINFO_EXTENSION); $unique_name = $client_code . '_' . time() . '_' . $i . '.' . $extension; $file_path = $upload_dir . $unique_name; if (move_uploaded_file($file_tmp, $file_path)) { $attachStmt = $pdo->prepare(" INSERT INTO client_attachments (client_id, file_name, file_path, file_type, file_size, uploaded_at) VALUES (?, ?, ?, ?, ?, NOW()) "); $attachStmt->execute([$client_id, $file_name, $file_path, $file_type, $file_size]); } } } } } logActivity($_SESSION['admin_id'], 'update_client', "Updated client #$client_id", 'client', $client_id); $success = 'Client updated successfully!'; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Update client error: " . $e->getMessage()); } } } // Handle attachment deletion - SEPARATE ACTION if ($_POST['action'] === 'delete_attachment') { $attach_id = intval($_POST['attachment_id'] ?? 0); if ($attach_id) { try { $pdo = getDBConnection(); $stmt = $pdo->prepare("SELECT * FROM client_attachments WHERE id = ? AND client_id = ?"); $stmt->execute([$attach_id, $client_id]); $attach = $stmt->fetch(); if ($attach) { if (file_exists($attach['file_path'])) { unlink($attach['file_path']); } $stmt = $pdo->prepare("DELETE FROM client_attachments WHERE id = ?"); $stmt->execute([$attach_id]); $success = 'Attachment deleted successfully!'; } } catch (Exception $e) { $error = 'Error deleting attachment.'; } } } } // Fetch client details try { $pdo = getDBConnection(); $stmt = $pdo->prepare("SELECT * FROM clients WHERE id = ?"); $stmt->execute([$client_id]); $client = $stmt->fetch(); if (!$client) { header('Location: clients.php'); exit; } // Fetch attachments $attachStmt = $pdo->prepare("SELECT * FROM client_attachments WHERE client_id = ?"); $attachStmt->execute([$client_id]); $attachments = $attachStmt->fetchAll(); } catch (Exception $e) { error_log("Fetch client error: " . $e->getMessage()); header('Location: clients.php'); exit; } $industries = [ 'Technology', 'Healthcare', 'Finance', 'Retail', 'Manufacturing', 'Education', 'Real Estate', 'Hospitality', 'Consulting', 'Marketing', 'Construction', 'Transportation', 'Other' ]; $countries = [ 'India', 'United States', 'United Kingdom', 'Canada', 'Australia', 'Germany', 'France', 'Japan', 'China', 'Singapore', 'United Arab Emirates', 'Saudi Arabia', 'Malaysia', 'Thailand', 'Indonesia', 'Philippines', 'Vietnam', 'South Korea', 'Hong Kong', 'Taiwan', 'Bangladesh', 'Pakistan', 'Sri Lanka', 'Nepal', 'Bhutan', 'Afghanistan', 'Maldives', 'Myanmar', 'Cambodia', 'Laos', 'Other' ]; include 'includes/header.php'; ?>

📎 Existing Documents ()

KB
Include country code (e.g., +91 for India, +1 for USA)
PDF, JPG, PNG, GIF (Max 5MB per file)
-------------------- END OF FILE -------------------- ### FILE 7: Relevant Reflex Shop/client_view.php - Type: PHP - Size: 11.71 KB - Path: Relevant Reflex Shop - Name: client_view.php ------------------------------------------------------------ prepare("SELECT * FROM clients WHERE id = ?"); $stmt->execute([$client_id]); $client = $stmt->fetch(); if (!$client) { header('Location: clients.php'); exit; } // Fetch attachments $attachStmt = $pdo->prepare("SELECT * FROM client_attachments WHERE client_id = ?"); $attachStmt->execute([$client_id]); $attachments = $attachStmt->fetchAll(); } catch (Exception $e) { error_log("View client error: " . $e->getMessage()); header('Location: clients.php'); exit; } include 'includes/header.php'; ?>

Basic Information

Company Name
Industry
Contact Person

Contact Information

Email Address
Phone Number
Website

Location Information

Address
City
Country
Postal Code

Business Information

Total Projects
Total Revenue
Account Created
Last Updated

Notes

Attachments ()

📎

No documents uploaded yet

KB • Uploaded on
📥 Download
-------------------- END OF FILE -------------------- ### FILE 8: Relevant Reflex Shop/clients.php - Type: PHP - Size: 13.34 KB - Path: Relevant Reflex Shop - Name: clients.php ------------------------------------------------------------ prepare("SELECT client_code FROM clients WHERE id = ?"); $stmt->execute([$delete_id]); $client_code = $stmt->fetchColumn(); $stmt = $pdo->prepare("DELETE FROM clients WHERE id = ?"); $stmt->execute([$delete_id]); logActivity($_SESSION['admin_id'], 'delete_client', "Deleted client: $client_code", 'client', $delete_id); $success = 'Client deleted successfully!'; } catch (Exception $e) { $error = 'Error deleting client.'; } } } // Fetch clients try { $pdo = getDBConnection(); $stmt = $pdo->query("SELECT * FROM clients ORDER BY created_at DESC"); $clients = $stmt->fetchAll(); } catch (Exception $e) { $clients = []; } $total_clients = count($clients); $active_clients = count(array_filter($clients, fn($c) => $c['status'] === 'active')); $total_projects = array_sum(array_column($clients, 'total_projects')); $total_revenue = array_sum(array_column($clients, 'total_revenue')); include 'includes/header.php'; ?>
Total Clients
Active Clients
Total Projects
Total Revenue
ID Client Code Company Name Industry Contact Person Location Email & Phone Projects Revenue Status Actions
No clients found. Click "Add New Client" to create one.
#
👁
-------------------- END OF FILE -------------------- ### FILE 9: Relevant Reflex Shop/config.php - Type: PHP - Size: 3.88 KB - Path: Relevant Reflex Shop - Name: config.php ------------------------------------------------------------ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); return $pdo; } catch (PDOException $e) { error_log("Connection failed: " . $e->getMessage()); die("Database connection failed. Please try again later."); } } // Create connection for Panel Database (Customer Portal) function getPanelDBConnection() { try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . PANEL_DB_NAME . ";charset=utf8mb4", PANEL_DB_USER, // Use the panel-specific user DB_PASS, // Assuming same password [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ] ); return $pdo; } catch (PDOException $e) { error_log("Panel DB Connection failed: " . $e->getMessage()); die("Panel database connection failed. Please try again later."); } } // Helper function to check if user is logged in function isLoggedIn() { return isset($_SESSION['admin_id']) && isset($_SESSION['admin_role']); } // Helper function to check if user is admin function isAdmin() { return isset($_SESSION['admin_role']) && $_SESSION['admin_role'] === 'admin'; } // Helper function to check if user can access page function canAccessPage($page) { if (!isLoggedIn()) { return false; } // Admin can access everything if (isAdmin()) { return true; } // Manager cannot access users page if ($page === 'users' && $_SESSION['admin_role'] === 'manager') { return false; } return true; } // Helper function to redirect if not authorized function requireLogin() { if (!isLoggedIn()) { header('Location: index.php'); exit; } } // Helper function to require admin role function requireAdmin() { requireLogin(); if (!isAdmin()) { header('Location: index.php'); exit; } } // Helper function to log admin activity function logActivity($admin_id, $action, $description, $resource_type = null, $resource_id = null) { try { $pdo = getDBConnection(); $stmt = $pdo->prepare(" INSERT INTO admin_activity_log (admin_id, action, resource_type, resource_id, description, ip_address) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([ $admin_id, $action, $resource_type, $resource_id, $description, $_SERVER['REMOTE_ADDR'] ?? null ]); } catch (Exception $e) { error_log("Activity log error: " . $e->getMessage()); } } ?> -------------------- END OF FILE -------------------- ### FILE 10: Relevant Reflex Shop/demand.php - Type: PHP - Size: 10.75 KB - Path: Relevant Reflex Shop - Name: demand.php ------------------------------------------------------------ 8.7, 'growth_rate' => 15.3, 'market_score' => 92, 'forecast_accuracy' => 87.5 ]; $demand_trends = [ ['category' => 'Technology Panels', 'demand_level' => 'High', 'growth' => '+23%', 'priority' => 'High'], ['category' => 'Healthcare Surveys', 'demand_level' => 'Medium', 'growth' => '+12%', 'priority' => 'Medium'], ['category' => 'Financial Services', 'demand_level' => 'High', 'growth' => '+18%', 'priority' => 'High'], ['category' => 'Retail Analytics', 'demand_level' => 'Low', 'growth' => '-5%', 'priority' => 'Low'], ['category' => 'Education Research', 'demand_level' => 'Medium', 'growth' => '+8%', 'priority' => 'Medium'] ]; $market_insights = [ ['insight' => 'Technology sector showing strongest demand growth', 'impact' => 'High', 'action' => 'Expand tech panel offerings'], ['insight' => 'Q3 demand exceeded forecasts by 15%', 'impact' => 'Medium', 'action' => 'Adjust Q4 capacity planning'], ['insight' => 'Mobile panel participation up 34%', 'impact' => 'High', 'action' => 'Optimize mobile experience'], ['insight' => 'Weekend response rates declining', 'impact' => 'Low', 'action' => 'Review scheduling strategy'] ]; include 'includes/header.php'; ?>
📈
Demand Index
+0.8 vs last month
🚀
%
Growth Rate
+2.3% vs Q2
🎯
Market Score
Excellent rating
🔮
%
Forecast Accuracy
+5.2% improvement

Demand Trends

Current Demand
Forecast

Category Performance

Key Market Insights

$insight): ?>
Impact

-------------------- END OF FILE -------------------- ### FILE 11: Relevant Reflex Shop/error-404.html - Type: HTML - Size: 6.9 KB - Path: Relevant Reflex Shop - Name: error-404.html ------------------------------------------------------------ Page Not Found - Relevant Reflex
404

Page Not Found

Sorry, the page you are looking for doesn't exist or has been moved. Let's get you back on track with our panel management system.

🏠 Back to Dashboard ⬅️ Go Back
-------------------- END OF FILE -------------------- ### FILE 12: Relevant Reflex Shop/error-500.html - Type: HTML - Size: 8.81 KB - Path: Relevant Reflex Shop - Name: error-500.html ------------------------------------------------------------ Server Error - Relevant Reflex
500

Internal Server Error

We're experiencing technical difficulties with our panel management system. Our team has been notified and is working to resolve this issue.

What you can try:

🏠 Back to Dashboard 📞 Contact Support

Need Immediate Assistance?

Our technical team is available 24/7 to help resolve system issues.

-------------------- END OF FILE -------------------- ### FILE 13: Relevant Reflex Shop/finance.php - Type: PHP - Size: 62.55 KB - Path: Relevant Reflex Shop - Name: finance.php ------------------------------------------------------------ prepare("SELECT id FROM company_settings WHERE setting_key = ?"); $stmt->execute([$key]); if ($stmt->fetch()) { $stmt = $pdo->prepare("UPDATE company_settings SET setting_value = ?, updated_by = ?, updated_at = NOW() WHERE setting_key = ?"); $stmt->execute([$value, $_SESSION['admin_id'], $key]); } else { $stmt = $pdo->prepare("INSERT INTO company_settings (setting_key, setting_value, updated_by, updated_at) VALUES (?, ?, ?, NOW())"); $stmt->execute([$key, $value, $_SESSION['admin_id']]); } } logActivity($_SESSION['admin_id'], 'update_pricing', 'Updated base pricing configuration', 'settings'); $success = 'Pricing configuration saved successfully!'; } catch (Exception $e) { error_log("Pricing save error: " . $e->getMessage()); $error = 'Error saving pricing: ' . $e->getMessage(); } } // Load current pricing values $pricing = []; try { $pdo = getDBConnection(); $stmt = $pdo->query("SELECT setting_key, setting_value, updated_at FROM company_settings WHERE setting_key LIKE 'pricing_%'"); while ($row = $stmt->fetch()) { $pricing[$row['setting_key']] = $row['setting_value']; $pricing[$row['setting_key'] . '_updated'] = $row['updated_at']; } } catch (Exception $e) { error_log("Pricing load error: " . $e->getMessage()); } // Helper to get pricing value function pv($key, $default = '') { global $pricing; return htmlspecialchars($pricing[$key] ?? $default); } // Get last updated time $last_updated = null; foreach ($pricing as $k => $v) { if (str_ends_with($k, '_updated') && $v) { if (!$last_updated || $v > $last_updated) { $last_updated = $v; } } } // === CSV Export (must run before any HTML output) === if ($tab === 'invoices' && isset($_GET['inv_export']) && $_GET['inv_export'] === 'csv') { $csvStatus=$_GET['inv_status']??''; $csvClient=$_GET['inv_client']??''; $csvDateFrom=$_GET['inv_from']??''; $csvDateTo=$_GET['inv_to']??''; $csvSearch=trim($_GET['inv_search']??''); $csvCurrency=$_GET['inv_currency']??''; $csvDueFrom=$_GET['inv_due_from']??''; $csvDueTo=$_GET['inv_due_to']??''; $csvSort=$_GET['inv_sort']??'invoice_date'; $csvDir=($_GET['inv_dir']??'desc')==='asc'?'ASC':'DESC'; $csvAllowed=['invoice_number','invoice_date','due_date','total_amount','status','project_code','client_name']; if (!in_array($csvSort,$csvAllowed)) $csvSort='invoice_date'; $csvWhere=["1=1"]; $csvParams=[]; if($csvStatus){$csvWhere[]="i.status=?";$csvParams[]=$csvStatus;} if($csvClient){$csvWhere[]="i.client_id=?";$csvParams[]=(int)$csvClient;} if($csvCurrency){$csvWhere[]="i.currency=?";$csvParams[]=$csvCurrency;} if($csvDateFrom){$csvWhere[]="i.invoice_date>=?";$csvParams[]=$csvDateFrom;} if($csvDateTo){$csvWhere[]="i.invoice_date<=?";$csvParams[]=$csvDateTo;} if($csvDueFrom){$csvWhere[]="i.due_date>=?";$csvParams[]=$csvDueFrom;} if($csvDueTo){$csvWhere[]="i.due_date<=?";$csvParams[]=$csvDueTo;} if($csvSearch){$csvWhere[]="(i.invoice_number LIKE ? OR p.project_name LIKE ? OR i.project_code LIKE ? OR c.company_name LIKE ?)";$s="%$csvSearch%";$csvParams=array_merge($csvParams,[$s,$s,$s,$s]);} $csvWc=implode(' AND ',$csvWhere); $stmt=$pdo->prepare("SELECT i.*,p.project_name,p.industry,c.company_name as client_company,c.contact_person FROM invoices i INNER JOIN projects p ON i.project_id=p.id INNER JOIN clients c ON i.client_id=c.id WHERE $csvWc ORDER BY $csvSort $csvDir"); $stmt->execute($csvParams); header('Content-Type: text/csv; charset=utf-8'); header('Content-Disposition: attachment; filename="invoices_'.date('Y-m-d_His').'.csv"'); $out=fopen('php://output','w'); fprintf($out,chr(0xEF).chr(0xBB).chr(0xBF)); fputcsv($out,['Invoice #','Client','Contact','Project','Project ID','Industry','Currency','Completes','Avg LOI','Rate/Sample','Total Amount','Invoice Date','Due Date','Paid Date','Status','Payment Ref','Remarks']); foreach($stmt->fetchAll(PDO::FETCH_ASSOC) as $r){ fputcsv($out,[$r['invoice_number'],$r['client_company'],$r['contact_person'],$r['project_name'],$r['project_code'],$r['industry'],$r['currency'],$r['valid_completes'],$r['avg_loi_minutes'],$r['rate_per_sample'],$r['total_amount'],$r['invoice_date'],$r['due_date'],$r['paid_date']??'',$r['status'],$r['payment_reference']??'',$r['remarks']??'']); } fclose($out);exit; } include 'includes/header.php'; ?>

Finance

Manage pricing, invoices & financial settings

💰 Pricing 📄 Invoices
🇮🇳
Base Currency
INR (₹)
Indian Rupee — all pricing in INR
🇺🇸
USD → INR
Loading...
Fetching live rate
🇪🇺
EUR → INR
Loading...
Fetching live rate

🏷 Base Rates Per Complete

Set the base cost per completed survey for an LOI of up to 5 minutes. These are the starting rates before any LOI increments are applied.

Cost charged to client per complete (LOI ≤ 5 min)
Incentive paid to panelist per complete (LOI ≤ 5 min)

⏱ LOI Increments Per Additional Minute

For surveys longer than 5 minutes, this amount is added per additional minute beyond the base 5 minutes.
Example: A 15-min survey adds 10 extra minutes × increment rate.

/min
Added to sample cost for each minute beyond 5 min LOI
/min
Added to incentive for each minute beyond 5 min LOI

💱 Currency Conversion Factors

These factors adjust the INR total before converting to foreign currency. The INR total (Sample Cost + Incentive) is divided by (100% + Factor%), then converted at the live exchange rate.
Example: Factor 10% → INR Total ÷ 1.10 = Adjusted INR → convert to USD/EUR.

%
Discount factor applied when pricing in USD
%
Discount factor applied when pricing in EUR
Last saved:

🔎 Quick Price Calculator

Test your pricing by entering an LOI. Uses the saved rates above and live exchange rates.

INR Price
USD Price
EUR Price
query("SELECT p.id, p.project_id as project_code, p.client_id FROM projects p LEFT JOIN invoices i ON i.project_id=p.id WHERE p.status='Closed' AND i.id IS NULL"); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $m) { if (function_exists('generateProjectInvoice')) generateProjectInvoice($pdo, $m['id'], $m['project_code'], $m['client_id']); } } catch (Exception $e) {} // Update statuses if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo); // Handle invoice POST actions if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) { $invAction = $_POST['action']; $invId = intval($_POST['invoice_id'] ?? 0); if ($invAction === 'mark_paid' && $invId) { $pdo->prepare("UPDATE invoices SET status='paid', paid_date=?, payment_reference=?, remarks=?, updated_at=NOW() WHERE id=?")->execute([$_POST['paid_date'] ?? date('Y-m-d'), trim($_POST['payment_reference'] ?? ''), trim($_POST['remarks'] ?? ''), $invId]); $success = 'Invoice marked as paid.'; logActivity($_SESSION['admin_id'], 'update', "Marked invoice #$invId as paid", 'invoice', $invId); } if ($invAction === 'save_remarks' && $invId) { $pdo->prepare("UPDATE invoices SET remarks=?, updated_at=NOW() WHERE id=?")->execute([trim($_POST['remarks'] ?? ''), $invId]); $success = 'Remarks saved.'; } if ($invAction === 'revert_unpaid' && $invId) { $pdo->prepare("UPDATE invoices SET status='invoiced', paid_date=NULL, payment_reference=NULL, updated_at=NOW() WHERE id=?")->execute([$invId]); if (function_exists('updateInvoiceStatuses')) updateInvoiceStatuses($pdo); $success = 'Invoice reverted to unpaid.'; } } // Ban enforcement try { $bannedClients = $pdo->query("SELECT DISTINCT client_id FROM invoices WHERE status != 'paid' AND due_date < DATE_SUB(CURDATE(), INTERVAL 90 DAY)")->fetchAll(PDO::FETCH_COLUMN); if (!empty($bannedClients)) { $ph = implode(',', array_fill(0, count($bannedClients), '?')); $pdo->prepare("UPDATE projects SET status='On hold' WHERE client_id IN ($ph) AND status='Live'")->execute($bannedClients); } } catch (Exception $e) {} // Invoice filters $fStatus=$_GET['inv_status']??''; $fClient=$_GET['inv_client']??''; $fDateFrom=$_GET['inv_from']??''; $fDateTo=$_GET['inv_to']??''; $fSearch=trim($_GET['inv_search']??''); $fCurrency=$_GET['inv_currency']??''; $fDueFrom=$_GET['inv_due_from']??''; $fDueTo=$_GET['inv_due_to']??''; $invSort=$_GET['inv_sort']??'invoice_date'; $invDir=($_GET['inv_dir']??'desc')==='asc'?'ASC':'DESC'; $invAllowed=['invoice_number','invoice_date','due_date','total_amount','status','project_code','client_name']; if (!in_array($invSort,$invAllowed)) $invSort='invoice_date'; $invWhere=["1=1"]; $invParams=[]; if($fStatus){$invWhere[]="i.status=?";$invParams[]=$fStatus;} if($fClient){$invWhere[]="i.client_id=?";$invParams[]=(int)$fClient;} if($fCurrency){$invWhere[]="i.currency=?";$invParams[]=$fCurrency;} if($fDateFrom){$invWhere[]="i.invoice_date>=?";$invParams[]=$fDateFrom;} if($fDateTo){$invWhere[]="i.invoice_date<=?";$invParams[]=$fDateTo;} if($fDueFrom){$invWhere[]="i.due_date>=?";$invParams[]=$fDueFrom;} if($fDueTo){$invWhere[]="i.due_date<=?";$invParams[]=$fDueTo;} if($fSearch){$invWhere[]="(i.invoice_number LIKE ? OR p.project_name LIKE ? OR i.project_code LIKE ? OR c.company_name LIKE ?)";$s="%$fSearch%";$invParams=array_merge($invParams,[$s,$s,$s,$s]);} $invWc=implode(' AND ',$invWhere); $stmt=$pdo->prepare("SELECT i.*,p.project_name,p.industry,c.company_name as client_company,c.contact_person FROM invoices i INNER JOIN projects p ON i.project_id=p.id INNER JOIN clients c ON i.client_id=c.id WHERE $invWc ORDER BY $invSort $invDir"); $stmt->execute($invParams); $allInvoices=$stmt->fetchAll(PDO::FETCH_ASSOC); $invSumAll=$invSumPaid=$invSumOut=0; $invSc=['invoiced'=>0,'paid'=>0,'due'=>0,'overdue'=>0,'critical'=>0,'banned'=>0]; foreach($allInvoices as $inv){ $invSumAll+=$inv['total_amount']; if($inv['status']==='paid')$invSumPaid+=$inv['total_amount'];else $invSumOut+=$inv['total_amount']; $invSc[$inv['status']]=($invSc[$inv['status']]??0)+1; } $allClients=$pdo->query("SELECT id,company_name FROM clients ORDER BY company_name")->fetchAll(PDO::FETCH_ASSOC); ?>
Total
Paid
Outstanding
📥 Download Invoices
All 'Invoiced','paid'=>'Paid','due'=>'Due','overdue'=>'Overdue','critical'=>'Critical','banned'=>'Banned'] as $sk=>$sl):?> 0):?>
Clear
📄

No invoices found. Invoices are auto-generated when projects are closed.

'','inv_dir'=>'']),['tab'=>'invoices']));?>
Invoice # Client Project Curr N Total Invoiced Due Status Actions
📄

* All amounts are inclusive of applicable taxes

-------------------- END OF FILE -------------------- ### FILE 14: Relevant Reflex Shop/generate_hash.php - Type: PHP - Size: 184 B - Path: Relevant Reflex Shop - Name: generate_hash.php ------------------------------------------------------------ php -------------------- END OF FILE -------------------- ### FILE 15: Relevant Reflex Shop/generate_panelbook.py - Type: PY - Size: 27.41 KB - Path: Relevant Reflex Shop - Name: generate_panelbook.py ------------------------------------------------------------ #!/usr/bin/env python3 """ Relevant Reflex — Panel Book PDF Generator Reads JSON data, produces a branded PDF with charts. Usage: python3 generate_panelbook.py input.json output.pdf """ import sys, json, io, math from datetime import datetime import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.ticker as mticker from reportlab.lib.pagesizes import A4 from reportlab.lib.units import mm, cm from reportlab.lib.colors import HexColor, white, black from reportlab.lib.styles import ParagraphStyle from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT from reportlab.platypus import ( SimpleDocTemplate, Paragraph, Spacer, Image, PageBreak, Table, TableStyle, KeepTogether ) from reportlab.lib import colors from reportlab.pdfgen import canvas # ─── Brand Palette ─── RR_GREEN = HexColor('#059669') RR_DARK = HexColor('#064e3b') RR_LIGHT = HexColor('#ecfdf5') SLATE_900 = HexColor('#0f172a') SLATE_700 = HexColor('#334155') SLATE_500 = HexColor('#64748b') SLATE_300 = HexColor('#cbd5e1') SLATE_100 = HexColor('#f1f5f9') SLATE_50 = HexColor('#f8fafc') WHITE = white # Chart color palette (muted, professional) CHART_COLORS = [ '#059669', '#0d9488', '#0891b2', '#0284c7', '#4f46e5', '#7c3aed', '#c026d3', '#e11d48', '#ea580c', '#d97706', '#65a30d', '#16a34a', '#94a3b8', '#64748b', '#475569', '#334155' ] W, H = A4 # 595.27, 841.89 # ────────────────────────────────────────────── # Chart Generators (return PNG bytes via BytesIO) # ────────────────────────────────────────────── def _apply_style(): plt.rcParams.update({ 'font.family': 'sans-serif', 'font.sans-serif': ['Helvetica', 'Arial', 'DejaVu Sans'], 'font.size': 8, 'axes.labelsize': 8, 'axes.titlesize': 9, 'xtick.labelsize': 7, 'ytick.labelsize': 7, 'figure.facecolor': 'white', 'axes.facecolor': 'white', 'axes.edgecolor': '#cbd5e1', 'axes.grid': False, 'axes.spines.top': False, 'axes.spines.right': False, }) def make_pie_chart(labels, values, title='', w_inch=3.8, h_inch=2.8): _apply_style() fig, ax = plt.subplots(figsize=(w_inch, h_inch)) clrs = CHART_COLORS[:len(labels)] wedges, texts, autotexts = ax.pie( values, labels=None, autopct='%1.1f%%', startangle=140, colors=clrs, pctdistance=0.75, wedgeprops=dict(width=0.5, edgecolor='white', linewidth=2) ) for t in autotexts: t.set_fontsize(7) t.set_color('#334155') t.set_fontweight('bold') ax.legend( [f'{l} ({v:,})' for l, v in zip(labels, values)], loc='center left', bbox_to_anchor=(1, 0.5), fontsize=7, frameon=False, labelspacing=0.8 ) if title: ax.set_title(title, fontsize=9, fontweight='bold', color='#0f172a', pad=8) fig.tight_layout(pad=0.5) buf = io.BytesIO() fig.savefig(buf, format='png', dpi=180, bbox_inches='tight', facecolor='white') plt.close(fig) buf.seek(0) return buf def make_hbar_chart(labels, values, title='', w_inch=5.2, h_inch=None, color='#059669'): _apply_style() n = len(labels) if h_inch is None: h_inch = max(1.6, min(n * 0.32 + 0.6, 7.5)) fig, ax = plt.subplots(figsize=(w_inch, h_inch)) # Truncate long labels short_labels = [l[:35] + '...' if len(str(l)) > 35 else str(l) for l in labels] y_pos = range(n) bars = ax.barh(y_pos, values, color=color, height=0.65, edgecolor='white', linewidth=0.5) ax.set_yticks(y_pos) ax.set_yticklabels(short_labels, fontsize=7, color='#334155') ax.invert_yaxis() ax.set_xlabel('') # Value labels on bars max_val = max(values) if values else 1 for bar, val in zip(bars, values): pct_of_total = (val / sum(values) * 100) if sum(values) > 0 else 0 ax.text(bar.get_width() + max_val * 0.02, bar.get_y() + bar.get_height()/2, f'{val:,} ({pct_of_total:.1f}%)', va='center', fontsize=6.5, color='#64748b') ax.set_xlim(0, max_val * 1.35) ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'{int(x):,}')) if title: ax.set_title(title, fontsize=9, fontweight='bold', color='#0f172a', pad=8, loc='left') ax.spines['left'].set_color('#e2e8f0') ax.spines['bottom'].set_color('#e2e8f0') ax.tick_params(axis='x', colors='#94a3b8') fig.tight_layout(pad=0.5) buf = io.BytesIO() fig.savefig(buf, format='png', dpi=180, bbox_inches='tight', facecolor='white') plt.close(fig) buf.seek(0) return buf def make_vbar_chart(labels, values, title='', w_inch=5.2, h_inch=2.5, color='#059669'): _apply_style() fig, ax = plt.subplots(figsize=(w_inch, h_inch)) x_pos = range(len(labels)) bars = ax.bar(x_pos, values, color=color, width=0.6, edgecolor='white', linewidth=0.5) ax.set_xticks(x_pos) ax.set_xticklabels(labels, fontsize=7, color='#334155', rotation=0) ax.set_ylabel('') for bar, val in zip(bars, values): ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + max(values)*0.02, f'{val:,}', ha='center', va='bottom', fontsize=7, color='#334155', fontweight='bold') ax.set_ylim(0, max(values) * 1.2 if values else 1) ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f'{int(x):,}')) if title: ax.set_title(title, fontsize=9, fontweight='bold', color='#0f172a', pad=8, loc='left') ax.spines['left'].set_color('#e2e8f0') ax.spines['bottom'].set_color('#e2e8f0') ax.tick_params(axis='y', colors='#94a3b8') fig.tight_layout(pad=0.5) buf = io.BytesIO() fig.savefig(buf, format='png', dpi=180, bbox_inches='tight', facecolor='white') plt.close(fig) buf.seek(0) return buf # ────────────────────────────────────────────── # Custom Page Template (header/footer) # ────────────────────────────────────────────── class PanelBookTemplate(SimpleDocTemplate): def __init__(self, *args, **kwargs): self.page_count = 0 self.is_cover = True super().__init__(*args, **kwargs) def afterPage(self): self.page_count += 1 def draw_page(canvas_obj, doc): """Draw header line and footer on every page except cover.""" canvas_obj.saveState() if doc.page_count == 0: # Cover page — no header/footer canvas_obj.restoreState() return page_num = doc.page_count + 1 # Top line canvas_obj.setStrokeColor(RR_GREEN) canvas_obj.setLineWidth(1.5) canvas_obj.line(30, H - 28, W - 30, H - 28) # Header text canvas_obj.setFont('Helvetica-Bold', 7) canvas_obj.setFillColor(SLATE_500) canvas_obj.drawString(32, H - 24, 'RELEVANT REFLEX') canvas_obj.setFont('Helvetica', 7) canvas_obj.drawRightString(W - 32, H - 24, 'Panel Book') # Footer canvas_obj.setStrokeColor(SLATE_300) canvas_obj.setLineWidth(0.5) canvas_obj.line(30, 32, W - 30, 32) canvas_obj.setFont('Helvetica', 6.5) canvas_obj.setFillColor(SLATE_500) canvas_obj.drawString(32, 20, 'Confidential — Relevant Reflex Panel Book') canvas_obj.drawRightString(W - 32, 20, f'Page {page_num}') canvas_obj.restoreState() # ────────────────────────────────────────────── # Styles # ────────────────────────────────────────────── def get_styles(): return { 'section_title': ParagraphStyle( 'SectionTitle', fontName='Helvetica-Bold', fontSize=16, textColor=SLATE_900, spaceAfter=4, leading=20 ), 'section_sub': ParagraphStyle( 'SectionSub', fontName='Helvetica', fontSize=8.5, textColor=SLATE_500, spaceAfter=16, leading=12 ), 'heading2': ParagraphStyle( 'Heading2', fontName='Helvetica-Bold', fontSize=11, textColor=RR_DARK, spaceAfter=6, spaceBefore=14, leading=14 ), 'body': ParagraphStyle( 'Body', fontName='Helvetica', fontSize=8.5, textColor=SLATE_700, spaceAfter=6, leading=12 ), 'body_center': ParagraphStyle( 'BodyCenter', fontName='Helvetica', fontSize=8.5, textColor=SLATE_700, spaceAfter=6, leading=12, alignment=TA_CENTER ), 'small': ParagraphStyle( 'Small', fontName='Helvetica', fontSize=7, textColor=SLATE_500, spaceAfter=4, leading=10 ), 'small_center': ParagraphStyle( 'SmallCenter', fontName='Helvetica', fontSize=7, textColor=SLATE_500, spaceAfter=4, leading=10, alignment=TA_CENTER ), 'metric_value': ParagraphStyle( 'MetricValue', fontName='Helvetica-Bold', fontSize=22, textColor=RR_GREEN, alignment=TA_CENTER, spaceAfter=0, leading=26 ), 'metric_label': ParagraphStyle( 'MetricLabel', fontName='Helvetica', fontSize=7.5, textColor=SLATE_500, alignment=TA_CENTER, spaceAfter=0, leading=10 ), 'question_title': ParagraphStyle( 'QuestionTitle', fontName='Helvetica-Bold', fontSize=9, textColor=SLATE_900, spaceAfter=2, spaceBefore=6, leading=12 ), 'question_sub': ParagraphStyle( 'QuestionSub', fontName='Helvetica', fontSize=7, textColor=SLATE_500, spaceAfter=6, leading=10 ), } def format_question(qid): """Convert question_id like 'education_level' to 'Education Level'.""" return qid.replace('_', ' ').title() # ────────────────────────────────────────────── # Build PDF # ────────────────────────────────────────────── def build_pdf(data, output_path): doc = PanelBookTemplate( output_path, pagesize=A4, topMargin=38, bottomMargin=44, leftMargin=32, rightMargin=32, title='Relevant Reflex Panel Book', author='Relevant Reflex' ) styles = get_styles() story = [] content_width = W - 64 # left + right margins # ════════════════════════════════════════════ # PAGE 1: COVER # ════════════════════════════════════════════ story.append(Spacer(1, 120)) # Logo block logo_data = [ [Paragraph('RR', ParagraphStyle('Logo', alignment=TA_CENTER))] ] logo_table = Table(logo_data, colWidths=[80], rowHeights=[64]) logo_table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), RR_GREEN), ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('ROUNDEDCORNERS', [12, 12, 12, 12]), ])) # Wrap logo in centered table logo_wrapper = Table([[logo_table]], colWidths=[content_width]) logo_wrapper.setStyle(TableStyle([('ALIGN', (0,0), (-1,-1), 'CENTER')])) story.append(logo_wrapper) story.append(Spacer(1, 24)) # Title story.append(Paragraph('Relevant Reflex', ParagraphStyle( 'CoverBrand', fontName='Helvetica-Bold', fontSize=32, textColor=RR_DARK, alignment=TA_CENTER, leading=38 ))) story.append(Spacer(1, 4)) story.append(Paragraph('Panel Book', ParagraphStyle( 'CoverTitle', fontName='Helvetica', fontSize=24, textColor=SLATE_500, alignment=TA_CENTER, leading=30 ))) story.append(Spacer(1, 10)) # Green divider divider_data = [[' ']] divider = Table(divider_data, colWidths=[80], rowHeights=[3]) divider.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), RR_GREEN), ('ROUNDEDCORNERS', [2, 2, 2, 2]), ])) div_wrapper = Table([[divider]], colWidths=[content_width]) div_wrapper.setStyle(TableStyle([('ALIGN', (0,0), (-1,-1), 'CENTER')])) story.append(div_wrapper) story.append(Spacer(1, 20)) # Generation info story.append(Paragraph( f'Generated on {data.get("generated_at", "")}', ParagraphStyle('CoverDate', fontName='Helvetica', fontSize=10, textColor=SLATE_700, alignment=TA_CENTER, leading=14) )) story.append(Spacer(1, 60)) # Disclaimer box disclaimer_text = ( 'All the data in this panel book are 100% based on the actual counts of the panel ' 'and not added/edited by human. This is a real-time snapshot generated at the ' 'date and time mentioned above.' ) disc_data = [[Paragraph(disclaimer_text, ParagraphStyle( 'Disclaimer', fontName='Helvetica', fontSize=7.5, textColor=SLATE_700, alignment=TA_CENTER, leading=11 ))]] disc_table = Table(disc_data, colWidths=[content_width * 0.75]) disc_table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), SLATE_50), ('BOX', (0,0), (-1,-1), 0.5, SLATE_300), ('TOPPADDING', (0,0), (-1,-1), 12), ('BOTTOMPADDING', (0,0), (-1,-1), 12), ('LEFTPADDING', (0,0), (-1,-1), 16), ('RIGHTPADDING', (0,0), (-1,-1), 16), ])) disc_wrapper = Table([[disc_table]], colWidths=[content_width]) disc_wrapper.setStyle(TableStyle([('ALIGN', (0,0), (-1,-1), 'CENTER')])) story.append(disc_wrapper) story.append(Spacer(1, 80)) # Footer note on cover story.append(Paragraph( 'www.relevantreflex.com', ParagraphStyle('CoverURL', fontName='Helvetica', fontSize=8, textColor=SLATE_500, alignment=TA_CENTER) )) story.append(PageBreak()) # ════════════════════════════════════════════ # PAGE 2: PANEL QUALITY OVERVIEW # ════════════════════════════════════════════ story.append(Paragraph('Panel Overview', styles['section_title'])) story.append(Paragraph('Key quality metrics and panel health indicators.', styles['section_sub'])) # Metrics grid (2 rows x 3 cols) verified = data.get('verified_members', 0) active = data.get('active_members', 0) total = data.get('total_members', 0) quality = data.get('quality', {}) def metric_cell(value, label): return [ Paragraph(str(value), styles['metric_value']), Paragraph(label, styles['metric_label']) ] metrics_data = [ [ metric_cell(f'{total:,}', 'Total Registered'), metric_cell(f'{active:,}', 'Active Members'), metric_cell(f'{verified:,}', 'Email Verified'), ], [ metric_cell(f'{quality.get("members_with_profiler", 0):,}', 'Profiler Completed'), metric_cell(f'{quality.get("mobile_verified", 0):,}', 'Mobile Verified'), metric_cell(f'{quality.get("avg_profiler_completion", 0):.0f}%', 'Avg. Profiler Completion'), ] ] # Flatten for Table (each metric_cell is a list of 2 paragraphs, put in inner table) def metric_inner(val_label_pair): t = Table([val_label_pair], colWidths=[content_width/3 - 12]) t.setStyle(TableStyle([ ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('TOPPADDING', (0,0), (-1,-1), 14), ('BOTTOMPADDING', (0,0), (-1,-1), 14), ])) return t row1 = [metric_inner(m) for m in metrics_data[0]] row2 = [metric_inner(m) for m in metrics_data[1]] col_w = content_width / 3 metrics_table = Table([row1, row2], colWidths=[col_w]*3, rowHeights=[72, 72]) metrics_table.setStyle(TableStyle([ ('GRID', (0,0), (-1,-1), 0.5, SLATE_300), ('BACKGROUND', (0,0), (-1,-1), SLATE_50), ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('ROUNDEDCORNERS', [8, 8, 8, 8]), ])) story.append(metrics_table) story.append(Spacer(1, 20)) # Project Stats if quality.get('total_projects', 0) > 0: story.append(Paragraph('Research Activity', styles['heading2'])) proj_data = [ ['Total Projects', 'Invitations Sent', 'Completed Surveys'], [ Paragraph(f'{quality.get("total_projects", 0):,}', styles['body_center']), Paragraph(f'{quality.get("total_surveys_sent", 0):,}', styles['body_center']), Paragraph(f'{quality.get("total_completes", 0):,}', styles['body_center']), ] ] proj_table = Table(proj_data, colWidths=[col_w]*3) proj_table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,0), RR_DARK), ('TEXTCOLOR', (0,0), (-1,0), WHITE), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'), ('FONTSIZE', (0,0), (-1,0), 7.5), ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('GRID', (0,0), (-1,-1), 0.5, SLATE_300), ('TOPPADDING', (0,0), (-1,-1), 8), ('BOTTOMPADDING', (0,0), (-1,-1), 8), ])) story.append(proj_table) # Profiler completion rates story.append(Spacer(1, 18)) story.append(Paragraph('Profiler Completion Rates', styles['heading2'])) prof_comp = data.get('profiler_completion', {}) prof_sections = data.get('profiler_sections', {}) comp_header = ['Section', 'Started', 'Completed'] comp_rows = [comp_header] for key, label in prof_sections.items(): pc = prof_comp.get(key, {}) comp_rows.append([ label, f'{pc.get("started", 0):,}', f'{pc.get("completed", 0):,}' ]) comp_table = Table(comp_rows, colWidths=[content_width * 0.55, content_width * 0.225, content_width * 0.225]) comp_table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,0), RR_DARK), ('TEXTCOLOR', (0,0), (-1,0), WHITE), ('FONTNAME', (0,0), (-1,0), 'Helvetica-Bold'), ('FONTSIZE', (0,0), (-1,-1), 7.5), ('FONTNAME', (0,1), (-1,-1), 'Helvetica'), ('ALIGN', (1,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('GRID', (0,0), (-1,-1), 0.5, SLATE_300), ('ROWBACKGROUNDS', (0,1), (-1,-1), [WHITE, SLATE_50]), ('TOPPADDING', (0,0), (-1,-1), 6), ('BOTTOMPADDING', (0,0), (-1,-1), 6), ('LEFTPADDING', (0,0), (0,-1), 10), ])) story.append(comp_table) story.append(PageBreak()) # ════════════════════════════════════════════ # PAGE 3: DEMOGRAPHICS # ════════════════════════════════════════════ story.append(Paragraph('Demographics', styles['section_title'])) story.append(Paragraph( f'Distribution of {verified:,} active, verified panel members.', styles['section_sub'] )) # Gender Pie gender_data = data.get('gender', []) if gender_data: labels = [g['gender'] or 'Not Specified' for g in gender_data] values = [g['count'] for g in gender_data] buf = make_pie_chart(labels, values, 'Gender Distribution') img = Image(buf, width=content_width * 0.75, height=content_width * 0.55) story.append(img) story.append(Spacer(1, 12)) # Age Bar age_data = data.get('age', []) if age_data: labels = [a['age_group'] for a in age_data] values = [a['count'] for a in age_data] buf = make_vbar_chart(labels, values, 'Age Distribution', w_inch=5.2, h_inch=2.5, color='#059669') img = Image(buf, width=content_width, height=content_width * 0.48) story.append(img) story.append(Spacer(1, 12)) # Geography Bar geo_data = data.get('geography', []) if geo_data: story.append(PageBreak()) story.append(Paragraph('Geographic Distribution', styles['section_title'])) story.append(Paragraph('Top 20 postcode regions by panel size.', styles['section_sub'])) labels = [g['region'] for g in geo_data] values = [g['count'] for g in geo_data] buf = make_hbar_chart(labels, values, 'Members by Postcode Prefix (Top 20)', color='#0d9488') h_ratio = max(1.6, min(len(labels) * 0.32 + 0.6, 7.5)) / 5.2 img = Image(buf, width=content_width, height=content_width * h_ratio) story.append(img) story.append(PageBreak()) # ════════════════════════════════════════════ # PAGES 4+: PROFILER SECTIONS # ════════════════════════════════════════════ profiler_data = data.get('profiler_data', {}) section_colors = list(CHART_COLORS) for sec_idx, (sec_key, sec_label) in enumerate(prof_sections.items()): if sec_key not in profiler_data: continue questions = profiler_data[sec_key] if not questions: continue sec_color = section_colors[sec_idx % len(section_colors)] # Section header story.append(Paragraph(sec_label, styles['section_title'])) pc = prof_comp.get(sec_key, {}) story.append(Paragraph( f'{len(questions)} question{"s" if len(questions) != 1 else ""} — ' f'{pc.get("completed", 0):,} members completed this section.', styles['section_sub'] )) for q_idx, (qid, qdata) in enumerate(questions.items()): dist = qdata.get('distribution', []) resp_count = qdata.get('respondent_count', 0) if not dist: continue labels = [d['label'] for d in dist] values = [d['count'] for d in dist] q_title = format_question(qid) # Decide chart type n_cats = len(dist) elements = [] elements.append(Paragraph(q_title, styles['question_title'])) elements.append(Paragraph( f'{resp_count:,} respondents', styles['question_sub'] )) if n_cats <= 5 and n_cats >= 2: # Pie chart for small category counts buf = make_pie_chart(labels, values, '', w_inch=3.8, h_inch=2.2) img = Image(buf, width=content_width * 0.72, height=content_width * 0.42) else: # Horizontal bar for larger category counts buf = make_hbar_chart(labels, values, '', color=sec_color) n = len(labels) h_ratio = max(1.4, min(n * 0.3 + 0.5, 6.5)) / 5.2 img = Image(buf, width=content_width * 0.92, height=content_width * h_ratio * 0.92) elements.append(img) elements.append(Spacer(1, 10)) # Try to keep question + chart together story.append(KeepTogether(elements)) story.append(PageBreak()) # ════════════════════════════════════════════ # LAST PAGE: CONTACT # ════════════════════════════════════════════ story.append(Spacer(1, 140)) # Logo contact_logo = [[Paragraph('RR', ParagraphStyle('CL', alignment=TA_CENTER))]] cl_table = Table(contact_logo, colWidths=[60], rowHeights=[48]) cl_table.setStyle(TableStyle([ ('BACKGROUND', (0,0), (-1,-1), RR_GREEN), ('ALIGN', (0,0), (-1,-1), 'CENTER'), ('VALIGN', (0,0), (-1,-1), 'MIDDLE'), ('ROUNDEDCORNERS', [10, 10, 10, 10]), ])) cl_wrapper = Table([[cl_table]], colWidths=[content_width]) cl_wrapper.setStyle(TableStyle([('ALIGN', (0,0), (-1,-1), 'CENTER')])) story.append(cl_wrapper) story.append(Spacer(1, 16)) story.append(Paragraph('Relevant Reflex', ParagraphStyle( 'ContactBrand', fontName='Helvetica-Bold', fontSize=20, textColor=RR_DARK, alignment=TA_CENTER, leading=24 ))) story.append(Spacer(1, 4)) story.append(Paragraph("India's Premier Online Research Panel", ParagraphStyle( 'ContactTag', fontName='Helvetica', fontSize=9, textColor=SLATE_500, alignment=TA_CENTER, leading=13 ))) story.append(Spacer(1, 30)) # Contact details contact_items = [ ('Email', 'contact@relevantreflex.com'), ('Support', 'support@relevantreflex.com'), ('Web', 'www.relevantreflex.com'), ('Client Portal', 'www.relevantreflex.com/clients'), ('Location', 'Tamilnadu, India'), ] for label, value in contact_items: story.append(Paragraph( f'{label}    ' f'{value}', ParagraphStyle('ContactItem', fontName='Helvetica', fontSize=8.5, alignment=TA_CENTER, leading=18, textColor=SLATE_700) )) story.append(Spacer(1, 40)) # Divider div2 = Table([[' ']], colWidths=[60], rowHeights=[2]) div2.setStyle(TableStyle([('BACKGROUND', (0,0), (-1,-1), SLATE_300)])) div2_w = Table([[div2]], colWidths=[content_width]) div2_w.setStyle(TableStyle([('ALIGN', (0,0), (-1,-1), 'CENTER')])) story.append(div2_w) story.append(Spacer(1, 14)) story.append(Paragraph( 'For panel inquiries, project feasibility, or partnership opportunities, ' 'please reach out to our client services team.', styles['small_center'] )) story.append(Spacer(1, 6)) story.append(Paragraph( f'This document was generated on {data.get("generated_at", "")}.', ParagraphStyle('FootNote', fontName='Helvetica', fontSize=6.5, textColor=SLATE_500, alignment=TA_CENTER, leading=9) )) # ════════════════════════════════════════════ # BUILD # ════════════════════════════════════════════ doc.build(story, onFirstPage=draw_page, onLaterPages=draw_page) print(f'PDF generated: {output_path}') # ─── Main ─── if __name__ == '__main__': if len(sys.argv) != 3: print('Usage: python3 generate_panelbook.py input.json output.pdf') sys.exit(1) with open(sys.argv[1], 'r') as f: data = json.load(f) build_pdf(data, sys.argv[2]) -------------------- END OF FILE -------------------- ### FILE 16: Relevant Reflex Shop/index.php - Type: PHP - Size: 66.4 KB - Path: Relevant Reflex Shop - Name: index.php ------------------------------------------------------------ prepare("SELECT * FROM admin_users WHERE username = ? AND status = 'active'"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { $_SESSION['admin_id'] = $user['id']; $_SESSION['admin_username'] = $user['username']; $_SESSION['admin_email'] = $user['email']; $_SESSION['admin_role'] = $user['role']; $_SESSION['admin_name'] = $user['full_name']; $updateStmt = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?"); $updateStmt->execute([$user['id']]); logActivity($user['id'], 'login', 'User logged in successfully'); header('Location: index.php'); exit; } else { $error = 'Invalid username or password'; } } catch (Exception $e) { error_log("Login error: " . $e->getMessage()); $error = 'An error occurred. Please try again.'; } } } // ============ SHOW LOGIN FORM ============ ?> Login - Relevant Reflex Admin

RELEVANT REFLEX

Admin Portal

🛠

Panel Management System

Manage members, projects, clients, invoices, fieldwork, and survey operations from a single unified dashboard.

Member & Panel Management
Client & Project Operations
Invoice & Finance Tracking
Fieldwork & Survey Monitoring

Welcome Back!

🛡

This is a secure admin area. All login attempts are logged and monitored. Unauthorized access is strictly prohibited.

Need help? Contact Support
0, 'members_active' => 0, 'members_verified' => 0, 'members_inactive' => 0, 'members_new' => 0, 'members_new_verified' => 0, 'members_mobile_verified' => 0, // Points economy 'points_circulation' => 0, 'points_total_earned' => 0, 'points_total_redeemed' => 0, 'points_earned_period' => 0, 'points_redeemed_period' => 0, // Redemptions 'redemptions_pending' => 0, 'redemptions_completed' => 0, 'redemptions_total' => 0, 'redemptions_pending_value' => 0, 'redemptions_period' => 0, 'redemptions_paid_period' => 0, // Tickets 'tickets_total' => 0, 'tickets_open' => 0, 'tickets_pending' => 0, 'tickets_resolved' => 0, 'tickets_closed' => 0, 'tickets_member' => 0, 'tickets_partner' => 0, 'tickets_client' => 0, 'tickets_period' => 0, // Affiliates 'affiliates_total' => 0, 'affiliates_active' => 0, 'affiliates_total_signups' => 0, 'affiliates_verified_signups' => 0, 'affiliates_total_commission' => 0, 'affiliates_commission_balance' => 0, 'affiliates_total_rewards_paid' => 0, 'affiliates_clicks_period' => 0, 'affiliates_signups_period' => 0, 'partner_redemptions_pending' => 0, 'partner_redemptions_pending_value' => 0, // Clients 'clients_total' => 0, 'clients_active' => 0, // Projects 'projects_total' => 0, 'projects_live' => 0, 'projects_created' => 0, 'projects_targeted' => 0, 'projects_onhold' => 0, 'projects_closed' => 0, 'projects_period' => 0, // Survey URLs 'urls_total' => 0, 'urls_available' => 0, 'urls_sent' => 0, 'urls_complete' => 0, 'urls_screenout' => 0, 'urls_quotafull' => 0, 'completes_period' => 0, // Member Availability (non-closed projects) 'avail_available' => 0, 'avail_selected' => 0, 'avail_assigned' => 0, 'avail_invited' => 0, 'avail_started' => 0, 'avail_completed' => 0, 'avail_screenout' => 0, 'avail_other_term' => 0, 'avail_live_projects' => 0, // Finance 'invoices_total' => 0, 'invoices_invoiced' => 0, 'invoices_paid' => 0, 'invoices_due' => 0, 'invoices_overdue' => 0, 'invoices_critical' => 0, 'revenue_total_inr' => 0, 'revenue_paid_inr' => 0, 'revenue_outstanding_inr' => 0, 'revenue_invoiced_inr' => 0, 'revenue_due_inr' => 0, 'revenue_overdue_inr' => 0, 'revenue_critical_inr' => 0, 'revenue_period_inr' => 0, // Admin 'admins_active' => 0, ]; $recent_activities = $recent_tickets = $recent_members_list = $recent_projects = []; // Get DB connections $pdo = null; $panelPdo = null; try { $pdo = getDBConnection(); $pdo->exec("SET time_zone = '+05:30'"); } catch (Exception $e) { error_log("Dashboard shop DB error: " . $e->getMessage()); } try { $panelPdo = getPanelDBConnection(); $panelPdo->exec("SET time_zone = '+05:30'"); } catch (Exception $e) { error_log("Dashboard panel DB error: " . $e->getMessage()); } // ─── MEMBERS ─── if ($panelPdo) { try { $d['members_total'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users")->fetchColumn(); $d['members_active'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE status='active'")->fetchColumn(); $d['members_verified'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE email_verified=1")->fetchColumn(); $d['members_inactive'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE status='inactive'")->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COUNT(*) FROM users WHERE created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['members_new'] = (int)$stmt->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COUNT(*) FROM users WHERE email_verified=1 AND created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['members_new_verified'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard members error: " . $e->getMessage()); } try { $d['members_mobile_verified'] = (int)$panelPdo->query("SELECT COUNT(*) FROM mobile_verifications WHERE is_verified=1")->fetchColumn(); } catch (Exception $e) { /* table may not exist */ } } // ─── POINTS ECONOMY ─── if ($panelPdo) { try { $d['points_circulation'] = (int)$panelPdo->query("SELECT COALESCE(SUM(points),0) FROM user_points")->fetchColumn(); $d['points_total_earned'] = (int)$panelPdo->query("SELECT COALESCE(SUM(total_earned),0) FROM user_points")->fetchColumn(); $d['points_total_redeemed'] = (int)$panelPdo->query("SELECT COALESCE(SUM(total_redeemed),0) FROM user_points")->fetchColumn(); } catch (Exception $e) { error_log("Dashboard user_points error: " . $e->getMessage()); } try { $stmt = $panelPdo->prepare("SELECT COALESCE(SUM(amount),0) FROM point_transactions WHERE type='credit' AND created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['points_earned_period'] = (int)$stmt->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COALESCE(SUM(amount),0) FROM point_transactions WHERE type='debit' AND created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['points_redeemed_period'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard point_transactions error: " . $e->getMessage()); } } // ─── REDEMPTIONS ─── if ($panelPdo) { try { $d['redemptions_pending'] = (int)$panelPdo->query("SELECT COUNT(*) FROM redemption_requests WHERE status='pending'")->fetchColumn(); $d['redemptions_completed'] = (int)$panelPdo->query("SELECT COUNT(*) FROM redemption_requests WHERE status='completed'")->fetchColumn(); $d['redemptions_total'] = (int)$panelPdo->query("SELECT COUNT(*) FROM redemption_requests")->fetchColumn(); $d['redemptions_pending_value'] = (float)$panelPdo->query("SELECT COALESCE(SUM(amount),0) FROM redemption_requests WHERE status='pending'")->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COUNT(*) FROM redemption_requests WHERE created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['redemptions_period'] = (int)$stmt->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COALESCE(SUM(amount),0) FROM redemption_requests WHERE status='completed' AND processed_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['redemptions_paid_period'] = (float)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard redemptions error: " . $e->getMessage()); } } // ─── SUPPORT TICKETS ─── if ($panelPdo) { try { $d['tickets_total'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets")->fetchColumn(); $d['tickets_open'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE status='open'")->fetchColumn(); $d['tickets_pending'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE status='pending'")->fetchColumn(); $d['tickets_resolved'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE status='resolved'")->fetchColumn(); $d['tickets_closed'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE status='closed'")->fetchColumn(); $d['tickets_member'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE sender_type='member'")->fetchColumn(); $d['tickets_partner'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE sender_type='partner'")->fetchColumn(); $d['tickets_client'] = (int)$panelPdo->query("SELECT COUNT(*) FROM support_tickets WHERE sender_type='client'")->fetchColumn(); $stmt = $panelPdo->prepare("SELECT COUNT(*) FROM support_tickets WHERE created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['tickets_period'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard tickets error: " . $e->getMessage()); } } // ─── AFFILIATES / PARTNERS ─── if ($pdo) { try { $d['affiliates_total'] = (int)$pdo->query("SELECT COUNT(*) FROM affiliates")->fetchColumn(); $d['affiliates_active'] = (int)$pdo->query("SELECT COUNT(*) FROM affiliates WHERE status='active'")->fetchColumn(); $d['affiliates_total_signups'] = (int)$pdo->query("SELECT COALESCE(SUM(total_signups),0) FROM affiliates")->fetchColumn(); $d['affiliates_verified_signups'] = (int)$pdo->query("SELECT COALESCE(SUM(total_verified_signups),0) FROM affiliates")->fetchColumn(); $d['affiliates_total_commission'] = (float)$pdo->query("SELECT COALESCE(SUM(total_commission_earned),0) FROM affiliates")->fetchColumn(); $d['affiliates_commission_balance'] = (float)$pdo->query("SELECT COALESCE(SUM(commission_balance),0) FROM affiliates")->fetchColumn(); $d['affiliates_total_rewards_paid'] = (float)$pdo->query("SELECT COALESCE(SUM(total_rewards_paid),0) FROM affiliates")->fetchColumn(); } catch (Exception $e) { error_log("Dashboard affiliates error: " . $e->getMessage()); } try { $stmt = $pdo->prepare("SELECT COUNT(*) FROM affiliate_signups WHERE clicked_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['affiliates_clicks_period'] = (int)$stmt->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM affiliate_signups WHERE signup_completed=1 AND signed_up_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['affiliates_signups_period'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard affiliate_signups error: " . $e->getMessage()); } try { $d['partner_redemptions_pending'] = (int)$pdo->query("SELECT COUNT(*) FROM partner_redemptions WHERE status='pending'")->fetchColumn(); $d['partner_redemptions_pending_value'] = (float)$pdo->query("SELECT COALESCE(SUM(amount),0) FROM partner_redemptions WHERE status='pending'")->fetchColumn(); } catch (Exception $e) { /* partner_redemptions table may not exist */ } } // ─── CLIENTS ─── if ($pdo) { try { $d['clients_total'] = (int)$pdo->query("SELECT COUNT(*) FROM clients")->fetchColumn(); $d['clients_active'] = (int)$pdo->query("SELECT COUNT(*) FROM clients WHERE status='active'")->fetchColumn(); } catch (Exception $e) { error_log("Dashboard clients error: " . $e->getMessage()); } } // ─── PROJECTS ─── if ($pdo) { try { $d['projects_total'] = (int)$pdo->query("SELECT COUNT(*) FROM projects")->fetchColumn(); $d['projects_live'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status='Live'")->fetchColumn(); $d['projects_created'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status='Created'")->fetchColumn(); $d['projects_targeted'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status='Targeted'")->fetchColumn(); $d['projects_onhold'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status='On hold'")->fetchColumn(); $d['projects_closed'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status='Closed'")->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM projects WHERE created_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['projects_period'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard projects error: " . $e->getMessage()); } } // ─── SURVEY URLs ─── if ($pdo) { try { $d['urls_total'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls")->fetchColumn(); $d['urls_available'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='available'")->fetchColumn(); $d['urls_sent'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='sent'")->fetchColumn(); $d['urls_complete'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='complete'")->fetchColumn(); $d['urls_screenout'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls WHERE status IN ('earlyscreenout','latescreenout')")->fetchColumn(); $d['urls_quotafull'] = (int)$pdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='quotafull'")->fetchColumn(); $stmt = $pdo->prepare("SELECT COUNT(*) FROM survey_urls WHERE status='complete' AND completed_at BETWEEN ? AND ?"); $stmt->execute([$dateFromSQL, $dateToSQL]); $d['completes_period'] = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Dashboard survey_urls error: " . $e->getMessage()); } } // ─── MEMBER AVAILABILITY (non-closed projects only) ─── if ($pdo) { try { // Count non-closed projects $d['avail_live_projects'] = (int)$pdo->query("SELECT COUNT(*) FROM projects WHERE status NOT IN ('Closed')")->fetchColumn(); // Selected: distinct members in selection_members for non-closed projects $d['avail_selected'] = (int)$pdo->query(" SELECT COUNT(DISTINCT sm.user_id) FROM selection_members sm JOIN project_selections ps ON sm.selection_id = ps.id JOIN projects p ON ps.project_id = p.id WHERE p.status NOT IN ('Closed') ")->fetchColumn(); // Survey URL stage counts (distinct members per stage, non-closed projects only) $urlStages = $pdo->query(" SELECT COUNT(DISTINCT CASE WHEN su.status = 'assigned' THEN su.sent_to_user_id END) as assigned_members, COUNT(DISTINCT CASE WHEN su.status = 'sent' THEN su.sent_to_user_id END) as sent_members, COUNT(DISTINCT CASE WHEN su.status = 'clicked' THEN su.sent_to_user_id END) as started_members, COUNT(DISTINCT CASE WHEN su.status = 'complete' THEN su.sent_to_user_id END) as completed_members, COUNT(DISTINCT CASE WHEN su.status IN ('earlyscreenout','latescreenout') THEN su.sent_to_user_id END) as screenout_members, COUNT(DISTINCT CASE WHEN su.status IN ('quotafull','timeout','partial') THEN su.sent_to_user_id END) as other_members FROM survey_urls su JOIN projects p ON su.project_id = p.project_id WHERE p.status NOT IN ('Closed') AND su.sent_to_user_id IS NOT NULL ")->fetch(); if ($urlStages) { $d['avail_assigned'] = (int)($urlStages['assigned_members'] ?? 0); $d['avail_invited'] = (int)($urlStages['sent_members'] ?? 0); $d['avail_started'] = (int)($urlStages['started_members'] ?? 0); $d['avail_completed'] = (int)($urlStages['completed_members'] ?? 0); $d['avail_screenout'] = (int)($urlStages['screenout_members'] ?? 0); $d['avail_other_term'] = (int)($urlStages['other_members'] ?? 0); } } catch (Exception $e) { error_log("Dashboard availability error: " . $e->getMessage()); } // Available: active+verified members NOT in any non-closed project if ($panelPdo) { try { $totalVerifiedActive = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE status='active' AND email_verified=1")->fetchColumn(); $d['avail_available'] = max(0, $totalVerifiedActive - $d['avail_selected']); } catch (Exception $e) { error_log("Dashboard avail_available error: " . $e->getMessage()); } } } // ─── INVOICES / FINANCE (multi-currency → INR) ─── $forexRates = ['INR' => 1.0, 'USD' => 86.50, 'EUR' => 93.00]; if ($pdo) { try { try { $ctx = stream_context_create(['http' => ['timeout' => 3]]); $rateJson = @file_get_contents('https://api.exchangerate-api.com/v4/latest/USD', false, $ctx); if ($rateJson) { $rateData = json_decode($rateJson, true); if (!empty($rateData['rates']['INR'])) { $usdToInr = (float)$rateData['rates']['INR']; $forexRates['USD'] = $usdToInr; if (!empty($rateData['rates']['EUR'])) { $forexRates['EUR'] = $usdToInr / (float)$rateData['rates']['EUR']; } } } } catch (Exception $e) { /* use fallback rates */ } $d['invoices_total'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices")->fetchColumn(); $d['invoices_invoiced'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices WHERE status='invoiced'")->fetchColumn(); $d['invoices_paid'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices WHERE status='paid'")->fetchColumn(); $d['invoices_due'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices WHERE status='due'")->fetchColumn(); $d['invoices_overdue'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices WHERE status='overdue'")->fetchColumn(); $d['invoices_critical'] = (int)$pdo->query("SELECT COUNT(*) FROM invoices WHERE status IN ('critical','banned')")->fetchColumn(); $amtStmt = $pdo->query(" SELECT status, currency, SUM(total_amount) as amt FROM invoices GROUP BY status, currency "); $amtRows = $amtStmt->fetchAll(); $statusMap = [ 'invoiced' => 'revenue_invoiced_inr', 'paid' => 'revenue_paid_inr', 'due' => 'revenue_due_inr', 'overdue' => 'revenue_overdue_inr', 'critical' => 'revenue_critical_inr', 'banned' => 'revenue_critical_inr', ]; foreach ($amtRows as $row) { $cur = $row['currency'] ?? 'INR'; $rate = $forexRates[$cur] ?? 1.0; $inr = (float)$row['amt'] * $rate; $key = $statusMap[$row['status']] ?? null; if ($key) $d[$key] += $inr; $d['revenue_total_inr'] += $inr; } $d['revenue_outstanding_inr'] = $d['revenue_total_inr'] - $d['revenue_paid_inr']; $stmt = $pdo->prepare("SELECT currency, SUM(total_amount) as amt FROM invoices WHERE created_at BETWEEN ? AND ? GROUP BY currency"); $stmt->execute([$dateFromSQL, $dateToSQL]); foreach ($stmt->fetchAll() as $row) { $rate = $forexRates[$row['currency'] ?? 'INR'] ?? 1.0; $d['revenue_period_inr'] += (float)$row['amt'] * $rate; } } catch (Exception $e) { /* invoices table may not exist */ } } // ─── ADMIN USERS ─── if ($pdo) { try { $d['admins_active'] = (int)$pdo->query("SELECT COUNT(*) FROM admin_users WHERE status='active'")->fetchColumn(); } catch (Exception $e) { /* */ } } // ─── RECENT ACTIVITIES ─── if ($pdo) { try { $activitiesStmt = $pdo->query(" SELECT al.*, au.full_name FROM admin_activity_log al LEFT JOIN admin_users au ON al.admin_id = au.id ORDER BY al.created_at DESC LIMIT 15 "); $recent_activities = $activitiesStmt->fetchAll(); } catch (Exception $e) { /* */ } } // ─── RECENT TICKETS ─── if ($panelPdo) { try { $recentTicketsStmt = $panelPdo->query("SELECT * FROM support_tickets ORDER BY updated_at DESC LIMIT 8"); $recent_tickets = $recentTicketsStmt->fetchAll(); } catch (Exception $e) { /* */ } } // ─── RECENT MEMBERS ─── if ($panelPdo) { try { $recentMembersStmt = $panelPdo->query("SELECT id, email, gender, email_verified, status, created_at FROM users ORDER BY created_at DESC LIMIT 8"); $recent_members_list = $recentMembersStmt->fetchAll(); } catch (Exception $e) { /* */ } } // ─── RECENT PROJECTS ─── if ($pdo) { try { $recentProjectsStmt = $pdo->query(" SELECT p.*, c.company_name as client_name FROM projects p LEFT JOIN clients c ON p.client_id = c.id ORDER BY p.updated_at DESC LIMIT 8 "); $recent_projects = $recentProjectsStmt->fetchAll(); } catch (Exception $e) { /* */ } } // Helper functions function fmtIST($dt, $fmt = 'd M Y, h:i A') { if (!$dt) return '—'; try { $date = new DateTime($dt, new DateTimeZone('Asia/Kolkata')); return $date->format($fmt); } catch (Exception $e) { return date($fmt, strtotime($dt)); } } function fmtISTDate($dt) { return fmtIST($dt, 'd M Y'); } function fmtISTTime($dt) { return fmtIST($dt, 'h:i A'); } function fmtINR($v) { return '₹' . number_format((float)$v, 2); } function fmtNum($v) { return number_format((int)$v); } include 'includes/header.php'; ?>

Dashboard

·

Today 7D 30D 90D MTD YTD | to
Showing data for
0) $alerts[] = ['warn', '💰 ' . fmtNum($d['redemptions_pending']) . ' pending member redemptions (' . fmtINR($d['redemptions_pending_value']) . ')', 'https://relevantreflex.com/support']; if (($d['partner_redemptions_pending'] ?? 0) > 0) $alerts[] = ['warn', '🤝 ' . fmtNum($d['partner_redemptions_pending']) . ' pending partner redemptions (' . fmtINR($d['partner_redemptions_pending_value']) . ')', 'https://relevantreflex.com/support']; if (($d['tickets_open'] ?? 0) > 0) $alerts[] = ['info', '🎫 ' . fmtNum($d['tickets_open']) . ' open support tickets', 'https://relevantreflex.com/support']; if (($d['projects_live'] ?? 0) > 0) $alerts[] = ['info', '🔴 ' . fmtNum($d['projects_live']) . ' live projects', 'https://relevantreflex.com/clients/projects.php']; ?>
👥 Panel Members
View All →
Total Members
+ in period
Active
0 ? round($d['members_active']/$d['members_total']*100) : 0; ?>% of total
Email Verified
+ in period
Mobile Verified
Inactive
📋 Member Availability non-closed project
Available
Verified active, not in any live project
Selected
Added to project selections
Assigned
URL assigned, not yet sent
Invited
Email sent
Started
Clicked survey link
Completed
Survey completed
Screen Out
Early / late screenout
Other Term.
Quota full, timeout, partial
Excludes closed projects • Counts distinct members
💎 Points Economy
In Circulation
Unredeemed balance
Lifetime Earned
+ in period
Lifetime Redeemed
in period
Pending Redemptions
value
Period Redemptions
paid out
🎫 Support Tickets
Manage →
Open
+ in period
Pending
Resolved
Closed
Total
M: P: C:
🤝 Affiliates / Partners
View All →
Active Affiliates
of total
Total Signups
+ in period
Verified Signups
0 ? round($d['affiliates_verified_signups']/$d['affiliates_total_signups']*100) : 0; ?>% conversion
Clicks (Period)
Commission Earned
Balance:
Rewards Paid
Pending Redemptions
to pay
📊 Clients, Projects & Fieldwork
Clients →
Active Clients
of total
Total Projects
+ in period
Live Projects
On Hold
Closed
Survey URLs
available
Completes
+ in period
Screen Outs
💰 Finance USD: ₹ · EUR: ₹
View Finance →
Total Invoiced
invoice · + in period
Paid
invoice
Outstanding
unpaid
Invoiced
Due
Overdue
Critical / Banned
Recent Projects View All →
ProjectClientStatusUpdated

'green','Closed'=>'gray','On hold'=>'amber','Created'=>'blue','Targeted'=>'blue'][$p['status']] ?? 'gray'; ?>

No projects yet

Recent Support Tickets Manage →
TicketTypeStatusUpdated

'red','pending'=>'amber','resolved'=>'green','closed'=>'gray'][$t['status']] ?? 'gray'; ?>

No tickets yet

Recent Members View All →
EmailGenderStatusJoined

No members yet

Admin Activity Log
AdminActionTime (IST)

No activity yet

🖥️ System
Admin Users: active Admin DB: u752449863_rrshop Panel DB: u752449863_rrpanel PHP: Server Time (IST): Timezone:
-------------------- END OF FILE -------------------- ### FILE 17: Relevant Reflex Shop/letterhead-download.php - Type: PHP - Size: 11.63 KB - Path: Relevant Reflex Shop - Name: letterhead-download.php ------------------------------------------------------------ query("SELECT setting_key, setting_value FROM company_settings"); while ($row = $result->fetch()) { $settings[$row['setting_key']] = $row['setting_value']; } } catch (Exception $e) { die('Error loading settings.'); } $s = function($key, $default = '') use ($settings) { return $settings[$key] ?? $default; }; // Build address parts $addrParts = array_filter([ $s('company_address'), $s('company_city'), $s('company_state'), $s('company_country'), $s('company_pincode') ]); $fullAddr = implode(', ', $addrParts); // Registration numbers $regNums = []; if ($s('tax_gst')) $regNums[] = ['label' => 'GSTIN', 'value' => $s('tax_gst')]; if ($s('tax_pan')) $regNums[] = ['label' => 'PAN', 'value' => $s('tax_pan')]; if ($s('tax_cin')) $regNums[] = ['label' => 'CIN', 'value' => $s('tax_cin')]; if ($s('tax_sac_code')) $regNums[] = ['label' => 'SAC', 'value' => $s('tax_sac_code')]; ?> Letterhead – <?php echo htmlspecialchars($s('company_name', 'Company')); ?>
← Back to Settings Preview your company letterhead
Panel Management & Research Solutions
🌐
:
-------------------- END OF FILE -------------------- ### FILE 18: Relevant Reflex Shop/login.php - Type: PHP - Size: 13.49 KB - Path: Relevant Reflex Shop - Name: login.php ------------------------------------------------------------ prepare("SELECT * FROM admin_users WHERE username = ? AND status = 'active'"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { // Set session $_SESSION['admin_id'] = $user['id']; $_SESSION['admin_username'] = $user['username']; $_SESSION['admin_email'] = $user['email']; $_SESSION['admin_role'] = $user['role']; $_SESSION['admin_name'] = $user['full_name']; // Update last login $updateStmt = $pdo->prepare("UPDATE admin_users SET last_login = NOW() WHERE id = ?"); $updateStmt->execute([$user['id']]); // Log activity logActivity($user['id'], 'login', 'User logged in successfully'); header('Location: index.php'); exit; } else { $error = 'Invalid username or password'; } } catch (Exception $e) { error_log("Login error: " . $e->getMessage()); $error = 'An error occurred. Please try again.'; } } } ?> Admin Login - Relevant Reflex

RELEVANT REFLEX

Admin Portal

🛠

Panel Management System

Manage members, projects, clients, invoices, fieldwork, and survey operations from a single unified dashboard.

Member & Panel Management
Client & Project Operations
Invoice & Finance Tracking
Fieldwork & Survey Monitoring

Welcome Back!

🛡

This is a secure admin area. All login attempts are logged and monitored. Unauthorized access is strictly prohibited.

Need help? Contact Support
-------------------- END OF FILE -------------------- ### FILE 19: Relevant Reflex Shop/logout.php - Type: PHP - Size: 185 B - Path: Relevant Reflex Shop - Name: logout.php ------------------------------------------------------------ -------------------- END OF FILE -------------------- ### FILE 20: Relevant Reflex Shop/maintenance.html - Type: HTML - Size: 0 B - Path: Relevant Reflex Shop - Name: maintenance.html ------------------------------------------------------------ -------------------- END OF FILE -------------------- ### FILE 21: Relevant Reflex Shop/member_edit.php - Type: PHP - Size: 14.06 KB - Path: Relevant Reflex Shop - Name: member_edit.php ------------------------------------------------------------ prepare(" UPDATE users SET gender = ?, date_of_birth = ?, postcode = ?, status = ?, updated_at = NOW() WHERE id = ? "); $stmt->execute([$gender, $dob, $postcode, $status, $member_id]); logActivity($_SESSION['admin_id'], 'update_member', "Updated member #$member_id basic info", 'member', $member_id); $success = 'Member information updated successfully!'; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Update member error: " . $e->getMessage()); } } } if ($_POST['action'] === 'update_mobile') { $mobile = trim($_POST['mobile_number'] ?? ''); if (empty($mobile)) { $error = 'Mobile number is required'; } else { try { $panelPdo = getPanelDBConnection(); // Check if mobile verification record exists $checkStmt = $panelPdo->prepare("SELECT id FROM mobile_verifications WHERE user_id = ?"); $checkStmt->execute([$member_id]); $exists = $checkStmt->fetch(); if ($exists) { $stmt = $panelPdo->prepare(" UPDATE mobile_verifications SET mobile_number = ?, updated_at = NOW() WHERE user_id = ? "); $stmt->execute([$mobile, $member_id]); } else { $stmt = $panelPdo->prepare(" INSERT INTO mobile_verifications (user_id, mobile_number) VALUES (?, ?) "); $stmt->execute([$member_id, $mobile]); } logActivity($_SESSION['admin_id'], 'update_member', "Updated member #$member_id mobile number", 'member', $member_id); $success = 'Mobile number updated successfully!'; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Update mobile error: " . $e->getMessage()); } } } } // Fetch member details try { $panelPdo = getPanelDBConnection(); $stmt = $panelPdo->prepare(" SELECT u.*, mv.mobile_number, mv.is_verified as mobile_verified FROM users u LEFT JOIN mobile_verifications mv ON u.id = mv.user_id WHERE u.id = ? "); $stmt->execute([$member_id]); $member = $stmt->fetch(); if (!$member) { header('Location: members.php'); exit; } } catch (Exception $e) { error_log("Fetch member error: " . $e->getMessage()); header('Location: members.php'); exit; } include 'includes/header.php'; ?>

Basic Information

Email address cannot be changed

Mobile Number

✓ Verified Not verified

Account Information

Email Verified
✓ Yes' : '✗ No'; ?>
Onboarding Completed
✓ Yes' : '⏳ No'; ?>
Member Since
Last Updated
-------------------- END OF FILE -------------------- ### FILE 22: Relevant Reflex Shop/member_view.php - Type: PHP - Size: 88.22 KB - Path: Relevant Reflex Shop - Name: member_view.php ------------------------------------------------------------ prepare(" SELECT u.*, up.points as current_points, up.total_earned, up.total_redeemed, mv.mobile_number, mv.is_verified as mobile_verified, mv.verified_at as mobile_verified_at FROM users u LEFT JOIN user_points up ON u.id = up.user_id LEFT JOIN mobile_verifications mv ON u.id = mv.user_id WHERE u.id = ? "); $stmt->execute([$member_id]); $member = $stmt->fetch(); if (!$member) { header('Location: panel.php'); exit; } // Fetch profiler completion $completionStmt = $panelPdo->prepare(" SELECT * FROM profiler_completion WHERE user_id = ? "); $completionStmt->execute([$member_id]); $completionData = $completionStmt->fetchAll(); // Fetch actual profiler responses for this member $profilerResponses = []; try { $respStmt = $panelPdo->prepare("SELECT section, question_id, response FROM user_profiler WHERE user_id = ? ORDER BY section, question_id"); $respStmt->execute([$member_id]); while ($row = $respStmt->fetch()) { if (!isset($profilerResponses[$row['section']])) $profilerResponses[$row['section']] = []; $profilerResponses[$row['section']][$row['question_id']] = json_decode($row['response'], true); } } catch (Exception $e) { $profilerResponses = []; } // Extract PAN & UPI details from profile section $memberPanNumber = $profilerResponses['profile']['pan_number'] ?? ''; $memberPanName = $profilerResponses['profile']['pan_name'] ?? ''; $memberPanStatus = $profilerResponses['profile']['pan_status'] ?? ''; if (!empty($memberPanNumber) && empty($memberPanStatus)) $memberPanStatus = 'pending'; $memberUpiId = $profilerResponses['profile']['upi_id'] ?? ''; // Profiler question definitions for human-readable labels $profilerDefs = [ 'personal_background' => [ 'name' => 'Personal Background', 'questions' => [ 'education_level' => ['q'=>'Highest level of education','opts'=>['below_10th'=>'Below 10th Standard','10th_pass'=>'10th Standard','12th_pass'=>'12th Standard/Intermediate','diploma'=>'Diploma/ITI','graduation'=>'Graduation','post_graduation'=>'Post Graduation','professional'=>'Professional Degree','doctorate'=>'Doctorate/PhD']], 'employment_status' => ['q'=>'Current employment status','opts'=>['student'=>'Student','employed_private'=>'Employed - Private Sector','employed_government'=>'Employed - Government/Public Sector','self_employed'=>'Self Employed/Business Owner','freelancer'=>'Freelancer/Consultant','homemaker'=>'Homemaker','retired'=>'Retired','unemployed'=>'Currently Unemployed']], 'occupation_sector' => ['q'=>'Work sector','opts'=>['it_software'=>'IT/Software','banking_finance'=>'Banking & Finance','healthcare'=>'Healthcare','education'=>'Education','manufacturing'=>'Manufacturing','retail'=>'Retail','media'=>'Media & Entertainment','government'=>'Government','telecom'=>'Telecommunications','fmcg'=>'FMCG','auto'=>'Automotive','real_estate'=>'Real Estate','agriculture'=>'Agriculture','legal'=>'Legal','other'=>'Other']], 'monthly_income' => ['q'=>'Monthly household income range','opts'=>['below_15000'=>'Below ₹15,000','15000_25000'=>'₹15,000 - ₹25,000','25000_50000'=>'₹25,000 - ₹50,000','50000_75000'=>'₹50,000 - ₹75,000','75000_100000'=>'₹75,000 - ₹1,00,000','100000_200000'=>'₹1,00,000 - ₹2,00,000','200000_500000'=>'₹2,00,000 - ₹5,00,000','above_500000'=>'Above ₹5,00,000','prefer_not_to_say'=>'Prefer not to say']], 'marital_status' => ['q'=>'Marital status','opts'=>['single'=>'Single','married'=>'Married','divorced'=>'Divorced','widowed'=>'Widowed','separated'=>'Separated','live_in'=>'Live-in Relationship']], 'language_primary' => ['q'=>'Primary language spoken at home','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','marathi'=>'Marathi','bengali'=>'Bengali','gujarati'=>'Gujarati','kannada'=>'Kannada','malayalam'=>'Malayalam','punjabi'=>'Punjabi','odia'=>'Odia','urdu'=>'Urdu','assamese'=>'Assamese','other'=>'Other']], 'languages_known' => ['q'=>'Languages you can read/write fluently','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','marathi'=>'Marathi','bengali'=>'Bengali','gujarati'=>'Gujarati','kannada'=>'Kannada','malayalam'=>'Malayalam','punjabi'=>'Punjabi','odia'=>'Odia','urdu'=>'Urdu','assamese'=>'Assamese','other'=>'Other']], 'area_type' => ['q'=>'Area type','opts'=>['metro'=>'Metro City','tier1'=>'Tier 1 City','tier2'=>'Tier 2 City','tier3'=>'Tier 3/Small Town','semi_urban'=>'Semi-Urban','rural'=>'Rural/Village']], ] ], 'household_family' => [ 'name' => 'Household & Family', 'questions' => [ 'household_size' => ['q'=>'People living in household','opts'=>['1'=>'Just me','2'=>'2 people','3'=>'3 people','4'=>'4 people','5'=>'5 people','6_plus'=>'6 or more']], 'children_count' => ['q'=>'Number of children','opts'=>['none'=>'No children','1'=>'1 child','2'=>'2 children','3'=>'3 children','4_plus'=>'4 or more']], 'children_age_group' => ['q'=>'Age groups of children','opts'=>['infant'=>'0-2 years (Infant)','toddler'=>'3-5 years (Toddler)','child'=>'6-12 years (Child)','teen'=>'13-17 years (Teen)','adult'=>'18+ years (Adult)','not_applicable'=>'Not Applicable']], 'home_ownership' => ['q'=>'Home ownership status','opts'=>['own_house'=>'Own House','rented'=>'Rented','family_owned'=>'Family Owned','company_provided'=>'Company Provided','paying_guest'=>'Paying Guest/Hostel']], 'vehicle_ownership' => ['q'=>'Vehicles owned','opts'=>['two_wheeler'=>'Two Wheeler (Bike/Scooter)','car_hatchback'=>'Car - Hatchback','car_sedan'=>'Car - Sedan','car_suv'=>'Car - SUV/MUV','ev_two_wheeler'=>'EV Two Wheeler','ev_car'=>'Electric Car','bicycle'=>'Bicycle','none'=>'No Vehicle','commercial'=>'Commercial Vehicle']], 'domestic_help' => ['q'=>'Domestic help employed','opts'=>['full_time'=>'Full-time','part_time'=>'Part-time','cook'=>'Cook only','driver'=>'Driver','none'=>'No domestic help']], 'decision_maker' => ['q'=>'Primary decision maker for purchases','opts'=>['self'=>'Self','spouse'=>'Spouse/Partner','parents'=>'Parents','joint'=>'Joint Decision','other'=>'Other Family Member']], 'pet_ownership' => ['q'=>'Pets owned','opts'=>['dog'=>'Dog','cat'=>'Cat','bird'=>'Bird','fish'=>'Fish','other_pet'=>'Other','no_pets'=>'No Pets']], ] ], 'shopping_lifestyle' => [ 'name' => 'Shopping & Lifestyle', 'questions' => [ 'online_shopping_frequency' => ['q'=>'Online shopping frequency','opts'=>['daily'=>'Almost daily','weekly'=>'Weekly','biweekly'=>'Every 2 weeks','monthly'=>'Monthly','quarterly'=>'Every few months','rarely'=>'Rarely','never'=>'Never']], 'preferred_ecommerce' => ['q'=>'Preferred e-commerce platforms','opts'=>['amazon'=>'Amazon','flipkart'=>'Flipkart','myntra'=>'Myntra','meesho'=>'Meesho','ajio'=>'AJIO','nykaa'=>'Nykaa','tatacliq'=>'Tata CLiQ','jiomart'=>'JioMart','snapdeal'=>'Snapdeal','bigbasket'=>'BigBasket','other'=>'Other']], 'monthly_online_spend' => ['q'=>'Monthly online shopping spend','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','10000_25000'=>'₹10,000 - ₹25,000','above_25000'=>'Above ₹25,000']], 'shopping_categories' => ['q'=>'Frequently purchased categories','opts'=>['electronics'=>'Electronics & Gadgets','fashion'=>'Fashion & Clothing','beauty'=>'Beauty & Personal Care','home_kitchen'=>'Home & Kitchen','grocery'=>'Groceries & Essentials','books'=>'Books & Stationery','sports'=>'Sports & Fitness','baby'=>'Baby & Kids','health'=>'Health & Wellness','jewelry'=>'Jewelry & Accessories','automotive'=>'Automotive Accessories']], 'brand_preference' => ['q'=>'Brand preference','opts'=>['premium'=>'Prefer premium brands','mid_range'=>'Prefer mid-range brands','value'=>'Prefer value/budget brands','no_preference'=>'No brand preference','mix'=>'Mix of all']], 'deal_influence' => ['q'=>'Influence of deals on purchases','opts'=>['always'=>'Always wait for deals','often'=>'Often look for deals','sometimes'=>'Sometimes check','rarely'=>'Rarely care about deals','never'=>'Never influenced by deals']], ] ], 'technology_digital' => [ 'name' => 'Technology & Digital', 'questions' => [ 'smartphone_brand' => ['q'=>'Current smartphone brand','opts'=>['samsung'=>'Samsung','apple'=>'Apple','xiaomi'=>'Xiaomi/Redmi','realme'=>'Realme','oppo'=>'OPPO','vivo'=>'Vivo','oneplus'=>'OnePlus','nothing'=>'Nothing','google'=>'Google Pixel','motorola'=>'Motorola','other'=>'Other']], 'smartphone_price' => ['q'=>'Smartphone price range','opts'=>['below_10000'=>'Below ₹10,000','10000_15000'=>'₹10,000 - ₹15,000','15000_25000'=>'₹15,000 - ₹25,000','25000_40000'=>'₹25,000 - ₹40,000','40000_60000'=>'₹40,000 - ₹60,000','above_60000'=>'Above ₹60,000']], 'internet_provider' => ['q'=>'Primary internet provider','opts'=>['jio'=>'Jio','airtel'=>'Airtel','vi'=>'Vi (Vodafone Idea)','bsnl'=>'BSNL','act'=>'ACT Fibernet','tataplay'=>'Tata Play Fiber','local_isp'=>'Local ISP','other'=>'Other']], 'daily_internet_hours' => ['q'=>'Daily internet usage hours','opts'=>['less_1'=>'Less than 1 hour','1_3'=>'1-3 hours','3_5'=>'3-5 hours','5_8'=>'5-8 hours','above_8'=>'More than 8 hours']], 'social_media_usage' => ['q'=>'Social media platforms used regularly','opts'=>['whatsapp'=>'WhatsApp','instagram'=>'Instagram','facebook'=>'Facebook','youtube'=>'YouTube','twitter'=>'Twitter/X','linkedin'=>'LinkedIn','snapchat'=>'Snapchat','telegram'=>'Telegram','koo'=>'Koo','pinterest'=>'Pinterest','reddit'=>'Reddit','threads'=>'Threads']], 'smart_devices' => ['q'=>'Smart devices owned','opts'=>['smart_tv'=>'Smart TV','smart_watch'=>'Smartwatch/Band','smart_speaker'=>'Smart Speaker','tablet'=>'Tablet/iPad','laptop'=>'Laptop','desktop'=>'Desktop PC','gaming_console'=>'Gaming Console','smart_home'=>'Smart Home Devices','none'=>'None of these']], 'streaming_services' => ['q'=>'Streaming services subscribed to','opts'=>['netflix'=>'Netflix','prime_video'=>'Amazon Prime Video','hotstar'=>'Disney+ Hotstar','sonyliv'=>'SonyLIV','zee5'=>'ZEE5','jiocinema'=>'JioCinema','youtube_premium'=>'YouTube Premium','spotify'=>'Spotify','apple_music'=>'Apple Music','none'=>'None']], ] ], 'travel_transportation' => [ 'name' => 'Travel & Transportation', 'questions' => [ 'daily_commute' => ['q'=>'Primary daily commute mode','opts'=>['own_car'=>'Own Car','own_bike'=>'Own Bike/Scooter','public_bus'=>'Public Bus','metro_train'=>'Metro/Local Train','auto_rickshaw'=>'Auto Rickshaw','cab_ola_uber'=>'Cab (Ola/Uber)','bicycle'=>'Bicycle','walk'=>'Walk','work_from_home'=>'Work from Home']], 'travel_frequency' => ['q'=>'Domestic travel frequency (per year)','opts'=>['never'=>'Rarely/Never','1_2'=>'1-2 times','3_5'=>'3-5 times','6_10'=>'6-10 times','above_10'=>'More than 10 times']], 'international_travel' => ['q'=>'International travel experience','opts'=>['never'=>'Never traveled abroad','once'=>'Once','2_5'=>'2-5 times','6_plus'=>'More than 5 times','frequent'=>'Frequent international traveler']], 'travel_booking' => ['q'=>'Travel booking platforms used','opts'=>['makemytrip'=>'MakeMyTrip','goibibo'=>'Goibibo','irctc'=>'IRCTC','booking_com'=>'Booking.com','cleartrip'=>'Cleartrip','yatra'=>'Yatra','airbnb'=>'Airbnb','oyo'=>'OYO','travel_agent'=>'Travel Agent','direct'=>'Direct with airline/hotel']], 'travel_preference' => ['q'=>'Travel accommodation preference','opts'=>['budget'=>'Budget Hotels','mid_range'=>'Mid-range Hotels','luxury'=>'Luxury Hotels','homestay'=>'Homestays','hostel'=>'Hostels/Backpacker','resort'=>'Resorts','relative'=>"Relative's/Friend's place"]], 'cab_usage' => ['q'=>'Ride-hailing usage frequency','opts'=>['daily'=>'Daily','weekly'=>'Few times a week','monthly'=>'Few times a month','rarely'=>'Rarely','never'=>'Never use']], ] ], 'health_fitness' => [ 'name' => 'Health & Fitness', 'questions' => [ 'exercise_frequency' => ['q'=>'Exercise frequency','opts'=>['daily'=>'Daily','4_6'=>'4-6 times a week','2_3'=>'2-3 times a week','once_week'=>'Once a week','rarely'=>'Rarely','never'=>'Never']], 'fitness_activities' => ['q'=>'Fitness activities','opts'=>['gym'=>'Gym/Weight Training','yoga'=>'Yoga','running'=>'Running/Jogging','walking'=>'Walking','swimming'=>'Swimming','cycling'=>'Cycling','sports'=>'Sports','dance'=>'Dance/Zumba','home_workout'=>'Home Workout','martial_arts'=>'Martial Arts','none'=>'None']], 'health_insurance' => ['q'=>'Health insurance coverage','opts'=>['employer'=>'Employer-provided','individual'=>'Individual Policy','family_floater'=>'Family Floater','government'=>'Government Scheme','no_insurance'=>'No Health Insurance']], 'diet_type' => ['q'=>'Dietary preference','opts'=>['vegetarian'=>'Vegetarian','non_vegetarian'=>'Non-Vegetarian','eggetarian'=>'Eggetarian','vegan'=>'Vegan','jain'=>'Jain','flexitarian'=>'Flexitarian']], 'health_conditions' => ['q'=>'Health conditions managed','opts'=>['diabetes'=>'Diabetes','hypertension'=>'Hypertension/BP','thyroid'=>'Thyroid','heart'=>'Heart Condition','asthma'=>'Asthma/Respiratory','allergies'=>'Allergies','obesity'=>'Obesity','none'=>'None','prefer_not'=>'Prefer not to say']], 'wellness_spend' => ['q'=>'Monthly health/wellness spend','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','above_10000'=>'Above ₹10,000']], 'health_tracking' => ['q'=>'Health/fitness tracking tools used','opts'=>['fitness_band'=>'Fitness Band/Smartwatch','phone_app'=>'Phone App','manual'=>'Manual Tracking','health_checkup'=>'Regular Health Checkups','none'=>'None']], ] ], 'entertainment_media' => [ 'name' => 'Entertainment & Media', 'questions' => [ 'content_language' => ['q'=>'Content consumption languages','opts'=>['hindi'=>'Hindi','english'=>'English','tamil'=>'Tamil','telugu'=>'Telugu','malayalam'=>'Malayalam','kannada'=>'Kannada','bengali'=>'Bengali','marathi'=>'Marathi','punjabi'=>'Punjabi','other'=>'Other']], 'entertainment_preference' => ['q'=>'Entertainment preferences','opts'=>['movies'=>'Movies','web_series'=>'Web Series','tv_shows'=>'TV Shows','music'=>'Music','podcasts'=>'Podcasts','gaming'=>'Gaming','reading'=>'Reading/Books','live_events'=>'Live Events/Concerts','sports_viewing'=>'Watching Sports']], 'news_source' => ['q'=>'Primary news sources','opts'=>['tv_news'=>'TV News Channels','newspaper'=>'Newspaper','news_apps'=>'News Apps','social_media'=>'Social Media','youtube'=>'YouTube','radio'=>'Radio','word_of_mouth'=>'Word of Mouth']], 'gaming_platform' => ['q'=>'Gaming platforms used','opts'=>['mobile'=>'Mobile Gaming','pc'=>'PC Gaming','playstation'=>'PlayStation','xbox'=>'Xbox','nintendo'=>'Nintendo','no_gaming'=>'Do not play games']], 'music_preference' => ['q'=>'Music listening platforms','opts'=>['spotify'=>'Spotify','youtube_music'=>'YouTube Music','jiosaavn'=>'JioSaavn','gaana'=>'Gaana','apple_music'=>'Apple Music','wynk'=>'Wynk','amazon_music'=>'Amazon Music','radio'=>'FM Radio','none'=>'None']], 'cinema_frequency' => ['q'=>'Movie theatre visits per month','opts'=>['never'=>'Never','once'=>'Once a month','2_3'=>'2-3 times a month','weekly'=>'Weekly','more'=>'More than weekly']], ] ], 'food_dining' => [ 'name' => 'Food & Dining', 'questions' => [ 'dining_out_frequency' => ['q'=>'Dining out/ordering frequency','opts'=>['daily'=>'Almost Daily','3_5_week'=>'3-5 times a week','1_2_week'=>'1-2 times a week','few_month'=>'Few times a month','rarely'=>'Rarely','never'=>'Never']], 'food_delivery_apps' => ['q'=>'Food delivery apps used','opts'=>['zomato'=>'Zomato','swiggy'=>'Swiggy','eatsure'=>'EatSure','dominos'=>'Domino\'s App','mcdonalds'=>'McDonald\'s App','other'=>'Other App','none'=>'Don\'t use delivery apps']], 'cuisine_preference' => ['q'=>'Favourite cuisines','opts'=>['north_indian'=>'North Indian','south_indian'=>'South Indian','chinese'=>'Chinese/Indo-Chinese','italian'=>'Italian/Pizza/Pasta','mughlai'=>'Mughlai/Biryani','street_food'=>'Street Food','continental'=>'Continental','japanese'=>'Japanese/Sushi','thai'=>'Thai','fast_food'=>'Fast Food/Burgers','healthy'=>'Healthy/Salads']], 'cooking_frequency' => ['q'=>'Home cooking frequency','opts'=>['all_meals'=>'All meals at home','mostly'=>'Most meals at home','half'=>'About half','rarely'=>'Rarely cook','never'=>'Never cook']], 'monthly_food_spend' => ['q'=>'Monthly food/dining spend','opts'=>['below_2000'=>'Below ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','5000_10000'=>'₹5,000 - ₹10,000','10000_20000'=>'₹10,000 - ₹20,000','above_20000'=>'Above ₹20,000']], 'beverage_preference' => ['q'=>'Daily beverages','opts'=>['tea'=>'Tea (Chai)','coffee'=>'Coffee','green_tea'=>'Green Tea','juice'=>'Fresh Juice','soft_drinks'=>'Soft Drinks/Cola','energy_drinks'=>'Energy Drinks','water_only'=>'Plain Water Only','milk'=>'Milk/Flavored Milk','alcohol'=>'Alcoholic Beverages']], ] ], 'financial_services' => [ 'name' => 'Financial Services', 'questions' => [ 'bank_account' => ['q'=>'Primary bank','opts'=>['sbi'=>'SBI','hdfc'=>'HDFC Bank','icici'=>'ICICI Bank','axis'=>'Axis Bank','kotak'=>'Kotak Mahindra','pnb'=>'PNB','bob'=>'Bank of Baroda','idfc'=>'IDFC First','yes'=>'Yes Bank','indusind'=>'IndusInd','other_private'=>'Other Private Bank','other_psu'=>'Other PSU Bank','payment_bank'=>'Payment Bank (Paytm/Airtel)']], 'credit_card' => ['q'=>'Credit card ownership','opts'=>['yes_multiple'=>'Yes, multiple cards','yes_one'=>'Yes, one card','no_debit'=>'No, use debit card only','no_card'=>'No cards']], 'credit_products' => ['q'=>'Credit products used','opts'=>['credit_card'=>'Credit card','personal_loan'=>'Personal loan','home_loan'=>'Home loan','car_loan'=>'Car loan','education_loan'=>'Education loan','bnpl'=>'Buy Now Pay Later','gold_loan'=>'Gold loan','none'=>'None']], 'investment_types' => ['q'=>'Investment instruments used','opts'=>['fd'=>'Fixed Deposits','mutual_funds'=>'Mutual Funds','stocks'=>'Direct Stocks','ppf'=>'PPF/EPF','gold'=>'Gold/Gold Bonds','real_estate'=>'Real Estate','nps'=>'NPS','crypto'=>'Cryptocurrency','insurance'=>'Insurance Plans','none'=>'None']], 'upi_usage' => ['q'=>'UPI/Digital payment usage','opts'=>['primary'=>'Primary payment method','frequent'=>'Frequently use','sometimes'=>'Sometimes use','rarely'=>'Rarely use','never'=>'Never use']], 'upi_apps' => ['q'=>'UPI apps used','opts'=>['gpay'=>'Google Pay','phonepe'=>'PhonePe','paytm'=>'Paytm','amazon_pay'=>'Amazon Pay','bhim'=>'BHIM','cred'=>'CRED','whatsapp_pay'=>'WhatsApp Pay','bank_app'=>'Bank\'s own app','other'=>'Other']], ] ], 'communication_payments' => [ 'name' => 'Communication & Payments', 'questions' => [ 'mobile_operator' => ['q'=>'Primary mobile operator','opts'=>['jio'=>'Jio','airtel'=>'Airtel','vi'=>'Vi (Vodafone Idea)','bsnl'=>'BSNL','mtnl'=>'MTNL','other'=>'Other']], 'mobile_plan_spend' => ['q'=>'Monthly mobile plan spend','opts'=>['below_200'=>'Below ₹200','200_500'=>'₹200 - ₹500','500_1000'=>'₹500 - ₹1,000','1000_2000'=>'₹1,000 - ₹2,000','above_2000'=>'Above ₹2,000']], 'sim_type' => ['q'=>'SIM type','opts'=>['prepaid'=>'Prepaid','postpaid'=>'Postpaid','both'=>'Both']], 'communication_apps' => ['q'=>'Communication apps used daily','opts'=>['whatsapp'=>'WhatsApp','telegram'=>'Telegram','signal'=>'Signal','messenger'=>'Facebook Messenger','teams'=>'Microsoft Teams','zoom'=>'Zoom','google_meet'=>'Google Meet','discord'=>'Discord','slack'=>'Slack']], 'digital_wallet_balance' => ['q'=>'Average digital wallet balance maintained','opts'=>['below_500'=>'Below ₹500','500_2000'=>'₹500 - ₹2,000','2000_5000'=>'₹2,000 - ₹5,000','above_5000'=>'Above ₹5,000','dont_maintain'=>'Don\'t maintain balance']], 'online_purchase_category' => ['q'=>'Categories of online purchases in last 3 months','opts'=>['electronics'=>'Electronics','fashion'=>'Fashion & Clothing','grocery'=>'Groceries','food_delivery'=>'Food Delivery','travel'=>'Travel/Booking','entertainment'=>'Entertainment/Subscriptions','education'=>'Education/Courses','health'=>'Health/Pharmacy','financial'=>'Financial/Insurance','products'=>'Product reviews and feedback','none'=>'None of these']], ] ] ]; // Fetch point transactions $transactionsStmt = $panelPdo->prepare(" SELECT * FROM point_transactions WHERE user_id = ? ORDER BY created_at DESC LIMIT 50 "); $transactionsStmt->execute([$member_id]); $transactions = $transactionsStmt->fetchAll(); // Fetch redemption requests $redemptionsStmt = $panelPdo->prepare(" SELECT * FROM redemption_requests WHERE user_id = ? ORDER BY created_at DESC "); $redemptionsStmt->execute([$member_id]); $redemptions = $redemptionsStmt->fetchAll(); // Fetch support tickets $ticketsStmt = $panelPdo->prepare(" SELECT * FROM support_tickets WHERE user_id = ? ORDER BY created_at DESC LIMIT 10 "); $ticketsStmt->execute([$member_id]); $tickets = $ticketsStmt->fetchAll(); // Fetch survey data for this member from admin DB $adminPdo = getDBConnection(); // 1. Selected: in selection_members but NO survey URL assigned for that project $selectedStmt = $adminPdo->prepare(" SELECT sm.selection_id, sm.sample_status, sm.assigned_at, ps.selection_name, ps.status as selection_status, ps.incentive_amount, ps.reward_per_complete, p.project_id, p.project_name, p.status as project_status, p.eloi, c.company_name as client_name FROM selection_members sm JOIN project_selections ps ON sm.selection_id = ps.id JOIN projects p ON ps.project_id = p.id JOIN clients c ON ps.client_id = c.id LEFT JOIN survey_urls su ON su.sent_to_user_id = sm.user_id AND su.project_id = p.project_id WHERE sm.user_id = ? AND su.id IS NULL ORDER BY sm.assigned_at DESC "); $selectedStmt->execute([$member_id]); $selectedSurveys = $selectedStmt->fetchAll(); // 2. Assigned: URL assigned but NOT yet sent (is_sent=0 and status='assigned') $assignedStmt = $adminPdo->prepare(" SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.created_at as url_created, ps.selection_name, ps.status as selection_status, p.project_id, p.project_name, p.status as project_status, p.eloi, c.company_name as client_name FROM survey_urls su JOIN projects p ON su.project_id = p.project_id JOIN clients c ON p.client_id = c.id LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id WHERE su.sent_to_user_id = ? AND su.is_sent = 0 AND su.status = 'assigned' ORDER BY su.created_at DESC "); $assignedStmt->execute([$member_id]); $assignedSurveys = $assignedStmt->fetchAll(); // 3. Sent: URL was sent (is_sent=1) but member has NOT interacted (no click/terminal status) $sentStmt = $adminPdo->prepare(" SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.sent_at, ps.selection_name, ps.status as selection_status, p.project_id, p.project_name, p.status as project_status, p.eloi, c.company_name as client_name FROM survey_urls su JOIN projects p ON su.project_id = p.project_id JOIN clients c ON p.client_id = c.id LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id WHERE su.sent_to_user_id = ? AND su.is_sent = 1 AND su.status NOT IN ('clicked','complete','partial','earlyscreenout','latescreenout','quotafull','timeout') ORDER BY su.sent_at DESC "); $sentStmt->execute([$member_id]); $sentSurveys = $sentStmt->fetchAll(); // 4. Taken: member actually interacted (clicked or has a terminal survey status) // Differentiate: "From Mail" (is_sent=1) vs "From Portal" (is_sent=0, took via Take Survey button) $takenStmt = $adminPdo->prepare(" SELECT su.id as url_id, su.unique_identifier, su.status as url_status, su.is_sent, su.clicked_at, su.completed_at, su.actual_loi_seconds, su.quality_flag, su.quality_notes, CASE WHEN su.is_sent = 1 THEN 'mail' ELSE 'portal' END as taken_source, ps.selection_name, ps.status as selection_status, ps.reward_per_complete, p.project_id, p.project_name, p.status as project_status, p.eloi, c.company_name as client_name FROM survey_urls su JOIN projects p ON su.project_id = p.project_id JOIN clients c ON p.client_id = c.id LEFT JOIN project_selections ps ON su.assigned_to_selection_id = ps.id WHERE su.sent_to_user_id = ? AND su.status IN ('clicked','complete','partial','earlyscreenout','latescreenout','quotafull','timeout') ORDER BY su.clicked_at DESC "); $takenStmt->execute([$member_id]); $takenSurveys = $takenStmt->fetchAll(); // Build taken status breakdown $takenByStatus = []; $takenBySource = ['mail' => 0, 'portal' => 0]; foreach ($takenSurveys as $t) { $s = $t['url_status']; if (!isset($takenByStatus[$s])) $takenByStatus[$s] = []; $takenByStatus[$s][] = $t; $takenBySource[$t['taken_source']]++; } } catch (Exception $e) { error_log("Member view error: " . $e->getMessage()); header('Location: panel.php'); exit; } include 'includes/header.php'; ?>
💰
Current Points
Available Balance
📈
Total Earned
Lifetime Earnings
💳
Total Redeemed
Lifetime Redemptions
Account Status

Basic Information

📧 Email Address
✓ Verified ✗ Not Verified
👤 Gender
🎂 Date of Birth
diff($dob)->y; echo $dob->format('F j, Y'); ?>
years old
📍 Postcode
📱 Mobile Number
✓ Verified
🔒 Account Status
✅ Email Verified
✓ Yes' : '✗ No'; ?>
🎯 Onboarding Status
✓ Completed ⏳ Pending
📅 Member Since
💐 Last Login

Payment & Tax Details

💳 UPI ID
Not provided'; ?>
📄 PAN Number
Not provided'; ?>
✓ Approved ✗ Rejected ⏳ Pending
👤 Name on PAN
Not provided'; ?>
⚙ PAN Verification

Survey Participation

Selected
In selection, no URL yet
Assigned
URL assigned, not mailed
Sent
Mailed, not yet taken
Taken
Clicked / has result
Taken — Source
📨 From Mail: 💻 From Portal:
Taken — Status Breakdown
'#059669','clicked'=>'#3b82f6','partial'=>'#f59e0b', 'earlyscreenout'=>'#ef4444','latescreenout'=>'#dc2626', 'quotafull'=>'#8b5cf6','timeout'=>'#6b7280' ]; $statusLabels = [ 'complete'=>'Complete','clicked'=>'Clicked','partial'=>'Partial', 'earlyscreenout'=>'Early Screen Out','latescreenout'=>'Late Screen Out', 'quotafull'=>'Quota Full','timeout'=>'Timeout' ]; foreach ($takenByStatus as $st => $items): $clr = $statusColors[$st] ?? '#6b7280'; $lbl = $statusLabels[$st] ?? ucfirst($st); ?> :
$items): $lbl = $statusLabels[$st] ?? ucfirst($st); ?>
📊
This member has no survey participation history yet.

Profiler Completion & Responses

$data) { if (!isset($allSections[$sec])) $allSections[$sec] = ['section'=>$sec,'completion_percentage'=>0,'answered_questions'=>0,'total_questions'=>count($data),'is_completed'=>0,'completed_at'=>null]; } ?> $completion): ?>
%
of questions answered • Completed on
📋
No profiler data available

Points Transaction History

💎
No transaction history available

Redemption Requests

Request ID Points Amount (₹) UPI ID Status Requested Processed
Pending'; ?>
🎁
No redemption requests found

Support Tickets

#
Created:
🎫
No support tickets found
-------------------- END OF FILE -------------------- ### FILE 23: Relevant Reflex Shop/members.php - Type: PHP - Size: 22.12 KB - Path: Relevant Reflex Shop - Name: members.php ------------------------------------------------------------ 0) { try { $panelPdo = getPanelDBConnection(); $panelPdo->beginTransaction(); // Delete related records first $panelPdo->prepare("DELETE FROM point_transactions WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_points WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM redemption_requests WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_tickets WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_messages WHERE sender_type = 'user' AND sender_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_profiler WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM profiler_completion WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM mobile_verifications WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?")->execute([$member_id]); // Delete the user $panelPdo->prepare("DELETE FROM users WHERE id = ?")->execute([$member_id]); $panelPdo->commit(); logActivity($_SESSION['admin_id'], 'delete_member', "Deleted member #$member_id", 'member', $member_id); $success_message = 'Member deleted successfully!'; } catch (Exception $e) { $panelPdo->rollBack(); error_log("Delete member error: " . $e->getMessage()); $error_message = 'Error deleting member. Please try again.'; } } } // Fetch all panel members with their points try { $panelPdo = getPanelDBConnection(); $stmt = $panelPdo->query(" SELECT u.*, up.points as current_points, up.total_earned, up.total_redeemed, mv.mobile_number, mv.is_verified as mobile_verified FROM users u LEFT JOIN user_points up ON u.id = up.user_id LEFT JOIN mobile_verifications mv ON u.id = mv.user_id ORDER BY u.created_at DESC "); $members = $stmt->fetchAll(); } catch (Exception $e) { $members = []; error_log("Fetch members error: " . $e->getMessage()); } include 'includes/header.php'; ?>
Total Members
$m['status'] === 'active')); ?>
Active Members
$m['email_verified'] == 1)); ?>
Email Verified
$m['mobile_verified'] == 1)); ?>
Mobile Verified
Member ID Email Gender Date of Birth Postcode Points Status Joined Actions
👥
No panel members found
Verified
diff($dob)->y; ?>
format('M j, Y'); ?>
years old
-------------------- END OF FILE -------------------- ### FILE 24: Relevant Reflex Shop/pan-action.php - Type: PHP - Size: 1.64 KB - Path: Relevant Reflex Shop - Name: pan-action.php ------------------------------------------------------------ false, 'error' => 'Invalid method']); exit; } $member_id = (int)($_POST['member_id'] ?? 0); $new_status = trim($_POST['status'] ?? ''); if (!$member_id || !in_array($new_status, ['pending', 'approved', 'rejected'])) { echo json_encode(['success' => false, 'error' => 'Invalid parameters']); exit; } try { $panelPdo = getPanelDBConnection(); // Verify member exists and has PAN submitted $stmt = $panelPdo->prepare("SELECT response FROM user_profiler WHERE user_id = ? AND section = 'profile' AND question_id = 'pan_number'"); $stmt->execute([$member_id]); $pan = $stmt->fetch(); if (!$pan || empty(json_decode($pan['response'], true))) { echo json_encode(['success' => false, 'error' => 'No PAN found for this member']); exit; } // Update PAN status $stmt = $panelPdo->prepare("INSERT INTO user_profiler (user_id, section, question_id, response) VALUES (?, 'profile', 'pan_status', ?) ON DUPLICATE KEY UPDATE response = ?, updated_at = NOW()"); $j = json_encode($new_status); $stmt->execute([$member_id, $j, $j]); echo json_encode(['success' => true, 'status' => $new_status]); } catch (Exception $e) { error_log("PAN action error: " . $e->getMessage()); echo json_encode(['success' => false, 'error' => 'Database error']); } -------------------- END OF FILE -------------------- ### FILE 25: Relevant Reflex Shop/panel.php - Type: PHP - Size: 22.19 KB - Path: Relevant Reflex Shop - Name: panel.php ------------------------------------------------------------ 0) { try { $panelPdo = getPanelDBConnection(); $panelPdo->beginTransaction(); // Delete related records first $panelPdo->prepare("DELETE FROM point_transactions WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_points WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM redemption_requests WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_tickets WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM support_messages WHERE sender_type = 'user' AND sender_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM user_profiler WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM profiler_completion WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM mobile_verifications WHERE user_id = ?")->execute([$member_id]); $panelPdo->prepare("DELETE FROM email_verifications WHERE user_id = ?")->execute([$member_id]); // Delete the user $panelPdo->prepare("DELETE FROM users WHERE id = ?")->execute([$member_id]); $panelPdo->commit(); logActivity($_SESSION['admin_id'], 'delete_member', "Deleted member #$member_id", 'member', $member_id); $success_message = 'Member deleted successfully!'; } catch (Exception $e) { $panelPdo->rollBack(); error_log("Delete member error: " . $e->getMessage()); $error_message = 'Error deleting member. Please try again.'; } } } try { $panelPdo = getPanelDBConnection(); $stmt = $panelPdo->query(" SELECT u.*, up.points as current_points, up.total_earned, up.total_redeemed, mv.mobile_number, mv.is_verified as mobile_verified FROM users u LEFT JOIN user_points up ON u.id = up.user_id LEFT JOIN mobile_verifications mv ON u.id = mv.user_id ORDER BY u.created_at DESC "); $members = $stmt->fetchAll(); } catch (Exception $e) { $members = []; error_log("Fetch members error: " . $e->getMessage()); } include 'includes/header.php'; ?>
Total Members
$m['status'] === 'active')); ?>
Active Members
$m['email_verified'] == 1)); ?>
Email Verified
$m['mobile_verified'] == 1)); ?>
Mobile Verified
$m['onboarding_completed'] == 1)); ?>
Onboarded
Total Points
Member ID Email Gender Date of Birth Postcode Points Status Joined Actions
👥
No panel members found
Verified
diff($dob)->y; ?>
format('M j, Y'); ?>
years old
-------------------- END OF FILE -------------------- ### FILE 26: Relevant Reflex Shop/panelbook-generate.php - Type: PHP - Size: 32.63 KB - Path: Relevant Reflex Shop - Name: panelbook-generate.php ------------------------------------------------------------ TCPDF not found'); } require_once $tcpdfPath; // ═══════════════════════════════════════════════ // 1. COLLECT ALL DATA // ═══════════════════════════════════════════════ try { $panelPdo = new PDO( 'mysql:host=localhost;dbname=u752449863_rrpanel;charset=utf8mb4', 'u752449863_rrpaneladmin', 'S@n@h2016', [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC] ); $shopPdo = getDBConnection(); } catch (Exception $e) { error_log("PanelBook DB: " . $e->getMessage()); header('Location: index.php?error=panelbook_db'); exit; } $d = []; $d['ts'] = date('F d, Y \a\t h:i A T'); $yr = date('Y'); $d['total'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users")->fetchColumn(); $d['active'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE status='active'")->fetchColumn(); $d['verified'] = (int)$panelPdo->query("SELECT COUNT(*) FROM users WHERE email_verified=1 AND status='active'")->fetchColumn(); $d['gender'] = $panelPdo->query("SELECT gender, COUNT(*) as c FROM users WHERE status='active' AND email_verified=1 GROUP BY gender ORDER BY c DESC")->fetchAll(); $s = $panelPdo->prepare(" SELECT CASE WHEN (?-YEAR(date_of_birth)) BETWEEN 18 AND 24 THEN '18-24' WHEN (?-YEAR(date_of_birth)) BETWEEN 25 AND 34 THEN '25-34' WHEN (?-YEAR(date_of_birth)) BETWEEN 35 AND 44 THEN '35-44' WHEN (?-YEAR(date_of_birth)) BETWEEN 45 AND 54 THEN '45-54' WHEN (?-YEAR(date_of_birth)) BETWEEN 55 AND 64 THEN '55-64' WHEN (?-YEAR(date_of_birth)) >= 65 THEN '65+' ELSE 'Unknown' END as ag, COUNT(*) as c FROM users WHERE status='active' AND email_verified=1 AND date_of_birth IS NOT NULL GROUP BY ag ORDER BY MIN(?-YEAR(date_of_birth)) "); $s->execute(array_fill(0, 7, $yr)); $d['age'] = $s->fetchAll(); // Geographic: Map PIN codes to Indian states $PIN_TO_STATE = [ // Delhi '110'=>'Delhi', // Haryana '12'=>'Haryana','13'=>'Haryana', // Punjab '14'=>'Punjab','15'=>'Punjab','16'=>'Punjab', // Chandigarh (override 3-digit) '160'=>'Chandigarh', // Himachal Pradesh '17'=>'Himachal Pradesh', // Jammu & Kashmir / Ladakh '18'=>'Jammu & Kashmir','19'=>'Jammu & Kashmir', '194'=>'Ladakh', // Uttar Pradesh '20'=>'Uttar Pradesh','21'=>'Uttar Pradesh','22'=>'Uttar Pradesh','23'=>'Uttar Pradesh', '24'=>'Uttar Pradesh','25'=>'Uttar Pradesh','27'=>'Uttar Pradesh','28'=>'Uttar Pradesh', // Uttarakhand '244'=>'Uttarakhand','245'=>'Uttarakhand','246'=>'Uttarakhand','247'=>'Uttarakhand', '248'=>'Uttarakhand','249'=>'Uttarakhand','26'=>'Uttarakhand', // Rajasthan '30'=>'Rajasthan','31'=>'Rajasthan','32'=>'Rajasthan','33'=>'Rajasthan','34'=>'Rajasthan', // Gujarat '36'=>'Gujarat','37'=>'Gujarat','38'=>'Gujarat','39'=>'Gujarat', // Goa '403'=>'Goa', // Maharashtra '40'=>'Maharashtra','41'=>'Maharashtra','42'=>'Maharashtra','43'=>'Maharashtra', '44'=>'Maharashtra','45'=>'Maharashtra', // Madhya Pradesh '46'=>'Madhya Pradesh','47'=>'Madhya Pradesh','48'=>'Madhya Pradesh', // Chhattisgarh '49'=>'Chhattisgarh', // Telangana '50'=>'Telangana','51'=>'Telangana', // Andhra Pradesh '52'=>'Andhra Pradesh','53'=>'Andhra Pradesh', // Karnataka '56'=>'Karnataka','57'=>'Karnataka','58'=>'Karnataka','59'=>'Karnataka', // Tamil Nadu '60'=>'Tamil Nadu','61'=>'Tamil Nadu','62'=>'Tamil Nadu','63'=>'Tamil Nadu','64'=>'Tamil Nadu', // Puducherry '605'=>'Puducherry', // Kerala '67'=>'Kerala','68'=>'Kerala','69'=>'Kerala', // West Bengal '70'=>'West Bengal','71'=>'West Bengal','72'=>'West Bengal','73'=>'West Bengal','74'=>'West Bengal', // Odisha '75'=>'Odisha','76'=>'Odisha', // Assam '78'=>'Assam', // NE States '790'=>'Manipur','791'=>'Manipur','795'=>'Manipur', '793'=>'Meghalaya', '796'=>'Mizoram', '797'=>'Nagaland', '799'=>'Tripura', '791'=>'Arunachal Pradesh','792'=>'Arunachal Pradesh', '737'=>'Sikkim', '77'=>'Assam', // Bihar '80'=>'Bihar','81'=>'Bihar','84'=>'Bihar','85'=>'Bihar', // Jharkhand '82'=>'Jharkhand','83'=>'Jharkhand', // Andaman & Nicobar '744'=>'Andaman & Nicobar', ]; function pinToState($postcode, $map) { $pc = preg_replace('/\s+/', '', $postcode); if (strlen($pc) < 2) return 'Unknown'; // Try 3-digit match first (more specific), then 2-digit $p3 = substr($pc, 0, 3); $p2 = substr($pc, 0, 2); if (isset($map[$p3])) return $map[$p3]; if (isset($map[$p2])) return $map[$p2]; return 'Other'; } // Fetch all postcodes and aggregate by state $geoStmt = $panelPdo->query(" SELECT postcode FROM users WHERE status='active' AND email_verified=1 AND postcode IS NOT NULL AND postcode!='' "); $stateCount = []; while ($row = $geoStmt->fetch()) { $state = pinToState($row['postcode'], $PIN_TO_STATE); $stateCount[$state] = ($stateCount[$state] ?? 0) + 1; } arsort($stateCount); $d['geo'] = []; foreach ($stateCount as $state => $cnt) { $d['geo'][] = ['r' => $state, 'c' => $cnt]; } $d['mob'] = 0; try { $d['mob'] = (int)$panelPdo->query("SELECT COUNT(*) FROM mobile_verifications WHERE is_verified=1")->fetchColumn(); } catch(Exception $e){} $d['avgp'] = 0; try { $d['avgp'] = round((float)$panelPdo->query("SELECT AVG(completion_percentage) FROM profiler_completion")->fetchColumn(),1); } catch(Exception $e){} $d['pan'] = 0; try { $d['pan'] = (int)$panelPdo->query("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section='profile' AND question_id='pan_status' AND response='\"approved\"'")->fetchColumn(); } catch(Exception $e){} $d['mwp'] = 0; try { $d['mwp'] = (int)$panelPdo->query("SELECT COUNT(DISTINCT user_id) FROM profiler_completion WHERE is_completed=1")->fetchColumn(); } catch(Exception $e){} $d['proj']=0; $d['sent']=0; $d['comp']=0; try { $d['proj'] = (int)$shopPdo->query("SELECT COUNT(*) FROM projects")->fetchColumn(); $d['sent'] = (int)$shopPdo->query("SELECT COUNT(*) FROM survey_urls WHERE is_sent=1")->fetchColumn(); $d['comp'] = (int)$shopPdo->query("SELECT COUNT(*) FROM survey_urls WHERE status='complete'")->fetchColumn(); } catch(Exception $e){} $SECTIONS = [ 'personal_background'=>'Personal Background','household_family'=>'Household & Family', 'shopping_lifestyle'=>'Shopping & Lifestyle','technology_digital'=>'Technology & Digital', 'travel_transportation'=>'Travel & Transportation','health_fitness'=>'Health & Fitness', 'entertainment_media'=>'Entertainment & Media','food_dining'=>'Food & Dining', 'financial_services'=>'Financial Services','communication_payments'=>'Communication & Payments' ]; $pcomp = []; foreach ($SECTIONS as $k => $l) { $s1 = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM profiler_completion WHERE section=? AND is_completed=1"); $s1->execute([$k]); $s2 = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section=?"); $s2->execute([$k]); $pcomp[$k] = ['done'=>(int)$s1->fetchColumn(), 'start'=>(int)$s2->fetchColumn()]; } $pdata = []; $qs = $panelPdo->query("SELECT DISTINCT section, question_id FROM user_profiler ORDER BY section, question_id")->fetchAll(); foreach ($qs as $q) { $sec = $q['section']; $qid = $q['question_id']; $rs = $panelPdo->prepare("SELECT response, COUNT(*) as c FROM user_profiler WHERE section=? AND question_id=? GROUP BY response ORDER BY c DESC"); $rs->execute([$sec, $qid]); $agg = []; while ($r = $rs->fetch()) { $dec = json_decode($r['response'], true); $c = (int)$r['c']; if (is_array($dec)) { foreach ($dec as $v) { $v=trim($v); if($v!=='') $agg[$v]=($agg[$v]??0)+$c; } } else { $v=trim($r['response'],'" '); if($v!=='') $agg[$v]=($agg[$v]??0)+$c; } } arsort($agg); $dist=[]; $i=0; $oth=0; foreach ($agg as $lb=>$ct) { if($i<12){$dist[]=['l'=>$lb,'c'=>$ct];$i++;}else{$oth+=$ct;} } if ($oth>0) $dist[]=['l'=>'Others','c'=>$oth]; $uc = $panelPdo->prepare("SELECT COUNT(DISTINCT user_id) FROM user_profiler WHERE section=? AND question_id=?"); $uc->execute([$sec,$qid]); if (!isset($pdata[$sec])) $pdata[$sec]=[]; $pdata[$sec][$qid] = ['dist'=>$dist, 'n'=>(int)$uc->fetchColumn()]; } // ═══════════════════════════════════════════════ // 2. FIND TTF FONT + GD CHART FUNCTIONS // ═══════════════════════════════════════════════ // Find a usable TrueType font — auto-download if needed function findFont($bold = false) { $tcpdfFonts = __DIR__ . '/tcpdf/fonts'; $cacheDir = __DIR__ . '/tcpdf/fonts'; $targetFile = $bold ? $cacheDir . '/DejaVuSans-Bold.ttf' : $cacheDir . '/DejaVuSans.ttf'; // 1. Check if we already have the font if (file_exists($targetFile) && filesize($targetFile) > 10000) { return $targetFile; } // 2. Search common system paths $systemPaths = $bold ? [ '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf', '/usr/share/fonts/dejavu-sans-fonts/DejaVuSans-Bold.ttf', '/usr/share/fonts/truetype/freefont/FreeSansBold.ttf', ] : [ '/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', '/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf', '/usr/share/fonts/truetype/freefont/FreeSans.ttf', ]; foreach ($systemPaths as $p) { if (file_exists($p)) return $p; } // 3. Scan TCPDF fonts dir for any .ttf if (is_dir($tcpdfFonts)) { $files = glob($tcpdfFonts . '/*.ttf'); if (!empty($files)) return $files[0]; } // 4. Auto-download DejaVu Sans from GitHub (one-time) $url = $bold ? 'https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans-Bold.ttf' : 'https://github.com/dejavu-fonts/dejavu-fonts/raw/master/ttf/DejaVuSans.ttf'; $ctx = stream_context_create(['http' => [ 'timeout' => 15, 'follow_location' => true, 'user_agent' => 'Mozilla/5.0' ]]); $fontData = @file_get_contents($url, false, $ctx); if ($fontData && strlen($fontData) > 10000) { if (is_writable($cacheDir)) { @file_put_contents($targetFile, $fontData); if (file_exists($targetFile)) return $targetFile; } // If cache dir not writable, write to /tmp $tmpFont = sys_get_temp_dir() . '/' . ($bold ? 'DejaVuSans-Bold.ttf' : 'DejaVuSans.ttf'); @file_put_contents($tmpFont, $fontData); if (file_exists($tmpFont)) return $tmpFont; } return null; // Will trigger fallback to built-in fonts } $FONT = findFont(false); $FONTB = findFont(true) ?: $FONT; // Wrapper: draws text using TTF if available, falls back to imagestring function drawText($img, $size, $x, $y, $text, $color, $font=null) { if ($font && function_exists('imagettftext')) { // imagettftext y is baseline, so offset imagettftext($img, $size, 0, $x, $y + $size + 2, $color, $font, $text); } else { // Fallback: use largest built-in font imagestring($img, 5, $x, $y, $text, $color); } } // Measure text width function textWidth($size, $text, $font=null) { if ($font && function_exists('imagettfbbox')) { $box = imagettfbbox($size, 0, $font, $text); return abs($box[2] - $box[0]); } return strlen($text) * 9; // approx for built-in font 5 } $CLRS = [ [5,150,105],[13,148,136],[8,145,178],[2,132,199],[79,70,229], [124,58,237],[192,38,211],[225,29,72],[234,88,12],[217,119,6], [101,163,13],[22,163,74],[148,163,184],[100,116,139],[71,85,105] ]; function gd_hbar($labels, $values, $clrs, $bcolor=null, $w=2000) { global $FONT, $FONTB; $n = count($labels); if(!$n) return null; $fontSize = 26; // label text size — larger to match pie legend $valSize = 24; // value text size $bH = 64; // bar height $gap = 16; // gap between bars $lPad = 520; // left padding for labels $rPad = 320; // right padding for values $tPad = 20; $botP = 14; $h = $tPad + $n*($bH+$gap) + $botP; $cW = $w-$lPad-$rPad; $mx = max($values)?:1; $tot = array_sum($values)?:1; $img = imagecreatetruecolor($w, $h); $wh = imagecolorallocate($img, 255, 255, 255); $tc = imagecolorallocate($img, 35, 45, 60); $mc = imagecolorallocate($img, 75, 90, 110); imagefill($img, 0, 0, $wh); $bc = $bcolor ?: $clrs[0]; $bar = imagecolorallocate($img, $bc[0], $bc[1], $bc[2]); for($i=0; $i<$n; $i++){ $y = $tPad + $i*($bH+$gap); $lb = mb_strlen($labels[$i])>32 ? mb_substr($labels[$i],0,30).'..' : $labels[$i]; // Label (right-aligned to left padding) $tw = textWidth($fontSize, $lb, $FONTB); drawText($img, $fontSize, max(10, $lPad - $tw - 20), $y + (int)(($bH-$fontSize)/2) - 4, $lb, $tc, $FONTB); // Bar $bW = max(5, (int)($values[$i]/$mx*$cW)); imagefilledrectangle($img, $lPad, $y+8, $lPad+$bW, $y+$bH-8, $bar); // Value $pct = round($values[$i]/$tot*100,1); $valText = number_format($values[$i])." ({$pct}%)"; drawText($img, $valSize, $lPad+$bW+14, $y + (int)(($bH-$valSize)/2) - 2, $valText, $mc, $FONT); } ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img); return $data; } function gd_vbar($labels, $values, $clrs, $w=1800, $h=600) { global $FONT, $FONTB; $n = count($labels); if(!$n) return null; $fontSize = 26; $valSize = 24; $lP=110; $rP=50; $tP=45; $bP=80; $cW=$w-$lP-$rP; $cH=$h-$tP-$bP; $mx=max($values)?:1; $bW=max(40, (int)($cW/$n*0.55)); $gap=(int)(($cW-$bW*$n)/($n+1)); $img = imagecreatetruecolor($w, $h); $wh = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $wh); $tc = imagecolorallocate($img, 35, 45, 60); $mc = imagecolorallocate($img, 130, 140, 160); $lc = imagecolorallocate($img, 226, 232, 240); $c = $clrs[0]; $bar = imagecolorallocate($img, $c[0], $c[1], $c[2]); // Grid lines for($g=0; $g<=4; $g++){ $gy = $tP + (int)($cH*(1-$g/4)); imageline($img, $lP, $gy, $w-$rP, $gy, $lc); $gLabel = number_format((int)($mx*$g/4)); drawText($img, 20, 8, $gy-14, $gLabel, $mc, $FONT); } for($i=0; $i<$n; $i++){ $x = $lP + $gap + $i*($bW+$gap); $bH2 = max(3, (int)($values[$i]/$mx*$cH)); $y = $tP+$cH-$bH2; imagefilledrectangle($img, $x, $y, $x+$bW, $tP+$cH, $bar); // Value on top $vS = number_format($values[$i]); $tw = textWidth($valSize, $vS, $FONTB); drawText($img, $valSize, $x+(int)($bW/2)-(int)($tw/2), $y-28, $vS, $tc, $FONTB); // Label below $lw = textWidth($fontSize, $labels[$i], $FONT); drawText($img, $fontSize, $x+(int)($bW/2)-(int)($lw/2), $tP+$cH+12, $labels[$i], $tc, $FONT); } ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img); return $data; } function gd_pie($labels, $values, $clrs, $sz=600) { global $FONT, $FONTB; $n = count($labels); if(!$n) return null; $tot = array_sum($values)?:1; $fontSize = 24; $legItemH = 58; // legend row height $legW = 780; $w = $sz + $legW + 70; $h = max($sz, $n*$legItemH + 60); $cx = (int)($sz/2)+30; $cy = (int)($h/2); $r = (int)($sz*0.42); $ir = (int)($r*0.52); $img = imagecreatetruecolor($w, $h); $wh = imagecolorallocate($img, 255, 255, 255); imagefill($img, 0, 0, $wh); $tc = imagecolorallocate($img, 35, 45, 60); imageantialias($img, true); // Draw pie slices $sa = -90; $sc = []; for($i=0; $i<$n; $i++){ $c = $clrs[$i % count($clrs)]; $sc[$i] = imagecolorallocate($img, $c[0], $c[1], $c[2]); $ang = ($values[$i]/$tot)*360; if($ang > 0.5) imagefilledarc($img, $cx, $cy, $r*2, $r*2, $sa, $sa+$ang, $sc[$i], IMG_ARC_PIE); $sa += $ang; } // Donut hole imagefilledellipse($img, $cx, $cy, $ir*2, $ir*2, $wh); // Legend $lx = $sz + 60; for($i=0; $i<$n; $i++){ $ly = 40 + $i*$legItemH; imagefilledrectangle($img, $lx, $ly, $lx+28, $ly+28, $sc[$i]); $pct = round($values[$i]/$tot*100, 1); $legText = $labels[$i]." (".number_format($values[$i])." - {$pct}%)"; drawText($img, $fontSize, $lx+40, $ly-2, $legText, $tc, $FONT); } ob_start(); imagepng($img); $data=ob_get_clean(); imagedestroy($img); return $data; } function fmtQ($q) { return ucwords(str_replace('_',' ',$q)); } // ═══════════════════════════════════════════════ // 3. BUILD PDF — 16:9 WIDESCREEN SLIDE // ═══════════════════════════════════════════════ define('SLIDE_W', 338.67); define('SLIDE_H', 190.5); class RRPDF extends TCPDF { public $isCover = true; public function Header() { if ($this->isCover) return; $pw = SLIDE_W; $this->SetY(6); $this->SetFont('helvetica','B',11); $this->SetTextColor(6,78,59); $this->SetXY(18, 6); $this->Cell(160, 8, 'RELEVANT REFLEX CONSULTING', 0, 0, 'L'); $this->SetFont('helvetica','',10); $this->SetTextColor(100,116,139); $this->SetXY($pw-18-120, 6); $this->Cell(120, 8, 'India Panel Book', 0, 0, 'R'); $this->SetDrawColor(5,150,105); $this->SetLineWidth(0.7); $this->Line(18, 16, $pw-18, 16); } public function Footer() { if ($this->isCover) return; $pw = SLIDE_W; $this->SetY(-14); $this->SetDrawColor(180,190,200); $this->SetLineWidth(0.25); $this->Line(18, $this->GetY(), $pw-18, $this->GetY()); $this->SetFont('helvetica','',8); $this->SetTextColor(100,116,139); $this->SetY(-12); $hw = ($pw-36)/2; $this->Cell($hw, 5, 'Confidential — Relevant Reflex Consulting', 0, 0, 'L'); $this->Cell($hw, 5, 'Page '.$this->getAliasNumPage(), 0, 0, 'R'); } } $pdf = new RRPDF('L', 'mm', array(SLIDE_W, SLIDE_H), true, 'UTF-8', false); $pdf->SetCreator('Relevant Reflex Consulting'); $pdf->SetAuthor('Relevant Reflex Consulting'); $pdf->SetTitle('Relevant Reflex Consulting — India Panel Book'); $pdf->SetMargins(22, 22, 22); $pdf->SetAutoPageBreak(true, 18); $PW = SLIDE_W - 44; $PH = SLIDE_H - 40; $LM = 22; $CX = SLIDE_W / 2; function insertChart($pdf, $chartData, $x, $y, $w, $h=0) { if (!$chartData) return; $tmp = tempnam(sys_get_temp_dir(),'rrchart_').'.png'; file_put_contents($tmp, $chartData); $pdf->Image($tmp, $x, $y, $w, $h, 'PNG'); @unlink($tmp); } // ══════════════════════════════════════════ // COVER PAGE // ══════════════════════════════════════════ $pdf->isCover = true; $pdf->AddPage(); $pdf->Ln(18); $pdf->SetFillColor(5,150,105); $pdf->RoundedRect($CX-24, $pdf->GetY(), 48, 30, 6, '1111', 'F'); $pdf->SetFont('helvetica','B',26); $pdf->SetTextColor(255,255,255); $pdf->SetXY($CX-24, $pdf->GetY()+3); $pdf->Cell(48, 24, 'RR', 0, 0, 'C'); $pdf->Ln(38); $pdf->SetFont('helvetica','B',38); $pdf->SetTextColor(6,78,59); $pdf->Cell(0, 15, 'Relevant Reflex Consulting', 0, 1, 'C'); $pdf->Ln(1); $pdf->SetFont('helvetica','',26); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 11, 'India Panel Book', 0, 1, 'C'); $pdf->Ln(3); $pdf->SetFillColor(5,150,105); $pdf->RoundedRect($CX-22, $pdf->GetY(), 44, 1.5, 0.75, '1111', 'F'); $pdf->Ln(8); $pdf->SetFont('helvetica','',13); $pdf->SetTextColor(51,65,85); $pdf->Cell(0, 7, 'Generated on '.$d['ts'], 0, 1, 'C'); $pdf->Ln(10); $pdf->SetFillColor(248,250,252); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(51,65,85); $disc = "All the data in this panel book are 100% based on the actual counts of the panel and not added/edited by human. This is a real-time snapshot generated at the date and time mentioned above."; $pdf->SetX(55); $pdf->MultiCell(SLIDE_W - 110, 6.5, $disc, 0, 'C', true); $pdf->Ln(10); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 5, 'www.relevantreflex.com', 0, 1, 'C'); // ══════════════════════════════════════════ // PANEL OVERVIEW // ══════════════════════════════════════════ $pdf->isCover = false; $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, 'Panel Overview', 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 6, 'Key quality metrics and panel health indicators — India panel.', 0, 1, 'L'); $pdf->Ln(6); $cw = $PW/3; $panPct = $d['verified'] > 0 ? round($d['pan'] / $d['verified'] * 100, 1) : 0; $mets = [ [[number_format($d['total']),'Total Registered'],[number_format($d['active']),'Active Members'],[number_format($d['verified']),'Email Verified']], [[number_format($d['mwp']),'Profiler Completed'],[number_format($d['mob']),'Mobile Verified'],[$panPct.'%','PAN Verified ('.number_format($d['pan']).')']], ]; foreach ($mets as $row) { $y0=$pdf->GetY(); foreach ($row as $ci=>$m) { if ($m[0] === '' && $m[1] === '') continue; $x=$LM+$ci*$cw; $pdf->SetFillColor(248,250,252); $pdf->RoundedRect($x, $y0, $cw-4, 28, 3, '1111', 'DF'); $pdf->SetFont('helvetica','B',28); $pdf->SetTextColor(5,150,105); $pdf->SetXY($x, $y0+1); $pdf->Cell($cw-4, 15, $m[0], 0, 0, 'C'); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(100,116,139); $pdf->SetXY($x, $y0+16); $pdf->Cell($cw-4, 9, $m[1], 0, 0, 'C'); } $pdf->SetY($y0+32); } $pdf->Ln(3); if ($d['proj']>0) { $pdf->SetFont('helvetica','B',15); $pdf->SetTextColor(6,78,59); $pdf->Cell(0, 8, 'Research Activity', 0, 1, 'L'); $pdf->Ln(2); $pdf->SetFillColor(6,78,59); $pdf->SetTextColor(255,255,255); $pdf->SetFont('helvetica','B',11); $pdf->Cell($cw, 9, 'Total Projects', 1, 0, 'C', true); $pdf->Cell($cw, 9, 'Invitations Sent', 1, 0, 'C', true); $pdf->Cell($cw, 9, 'Completed Surveys', 1, 1, 'C', true); $pdf->SetTextColor(51,65,85); $pdf->SetFont('helvetica','B',14); $pdf->Cell($cw, 11, number_format($d['proj']), 1, 0, 'C'); $pdf->Cell($cw, 11, number_format($d['sent']), 1, 0, 'C'); $pdf->Cell($cw, 11, number_format($d['comp']), 1, 1, 'C'); } // ══════════════════════════════════════════ // PROFILER COMPLETION RATES // ══════════════════════════════════════════ $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, 'Profiler Completion Rates', 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $popBase = $d['verified'] > 0 ? $d['verified'] : $d['active']; $pdf->Cell(0, 6, 'Percentage based on '.number_format($popBase).' verified active panel members.', 0, 1, 'L'); $pdf->Ln(5); $pdf->SetFillColor(6,78,59); $pdf->SetTextColor(255,255,255); $pdf->SetFont('helvetica','B',12); $pdf->Cell($PW*0.46, 10, ' Section', 1, 0, 'L', true); $pdf->Cell($PW*0.18, 10, 'Completed', 1, 0, 'C', true); $pdf->Cell($PW*0.18, 10, '% of Panel', 1, 0, 'C', true); $pdf->Cell($PW*0.18, 10, 'Status', 1, 1, 'C', true); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(51,65,85); $ev=false; foreach ($SECTIONS as $k=>$lb) { $pc=$pcomp[$k]; $pct = $popBase > 0 ? round($pc['done'] / $popBase * 100, 1) : 0; $status = $pct >= 50 ? 'Strong' : ($pct >= 20 ? 'Growing' : ($pct > 0 ? 'Emerging' : '—')); if($ev) $pdf->SetFillColor(248,250,252); else $pdf->SetFillColor(255,255,255); $pdf->Cell($PW*0.46, 8, ' '.$lb, 'LR', 0, 'L', true); $pdf->Cell($PW*0.18, 8, number_format($pc['done']), 'LR', 0, 'C', true); $pdf->SetFont('helvetica','B',11); if ($pct >= 50) $pdf->SetTextColor(5,150,105); elseif ($pct >= 20) $pdf->SetTextColor(217,119,6); else $pdf->SetTextColor(100,116,139); $pdf->Cell($PW*0.18, 8, $pct.'%', 'LR', 0, 'C', true); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(51,65,85); $pdf->Cell($PW*0.18, 8, $status, 'LR', 1, 'C', true); $ev=!$ev; } $pdf->SetDrawColor(203,213,225); $pdf->Line($LM, $pdf->GetY(), $LM+$PW, $pdf->GetY()); // ══════════════════════════════════════════ // DEMOGRAPHICS // ══════════════════════════════════════════ $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, 'Demographics', 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 6, 'Distribution of '.number_format($d['verified']).' active, verified panel members across India.', 0, 1, 'L'); $pdf->Ln(4); if (!empty($d['gender'])) { $pdf->SetFont('helvetica','B',15); $pdf->SetTextColor(6,78,59); $pdf->Cell(0, 8, 'Gender Distribution', 0, 1, 'L'); $pdf->Ln(2); $lb=array_column($d['gender'],'gender'); $vl=array_map('intval',array_column($d['gender'],'c')); insertChart($pdf, gd_pie($lb,$vl,$CLRS,560), 30, $pdf->GetY(), 220); $pdf->Ln(72); } if (!empty($d['age'])) { $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, 'Age Distribution', 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 6, 'Age brackets of active verified panel members.', 0, 1, 'L'); $pdf->Ln(3); $lb=array_column($d['age'],'ag'); $vl=array_map('intval',array_column($d['age'],'c')); insertChart($pdf, gd_vbar($lb,$vl,$CLRS,1800,560), $LM, $pdf->GetY(), $PW); $pdf->Ln(70); } // ══════════════════════════════════════════ // GEOGRAPHY // ══════════════════════════════════════════ if (!empty($d['geo'])) { $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, 'Geographic Distribution', 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 6, 'Panel member distribution across '.count($d['geo']).' Indian states & territories.', 0, 1, 'L'); $pdf->Ln(3); $allLabels = array_column($d['geo'],'r'); $allValues = array_map('intval', array_column($d['geo'],'c')); $total = count($allLabels); if ($total <= 15) { // Fits on one page $ch = gd_hbar($allLabels, $allValues, $CLRS, [13,148,136], 2000); $imgH = min($total*9.5+8, $PH-22); insertChart($pdf, $ch, $LM, $pdf->GetY(), $PW, $imgH); } else { // Split across two pages $half = (int)ceil($total/2); $lb1 = array_slice($allLabels, 0, $half); $vl1 = array_slice($allValues, 0, $half); $ch1 = gd_hbar($lb1, $vl1, $CLRS, [13,148,136], 2000); $imgH1 = min(count($lb1)*9.5+8, $PH-22); insertChart($pdf, $ch1, $LM, $pdf->GetY(), $PW, $imgH1); $lb2 = array_slice($allLabels, $half); $vl2 = array_slice($allValues, $half); if (!empty($lb2)) { $pdf->AddPage(); $pdf->SetFont('helvetica','B',18); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 9, 'Geographic Distribution (continued)', 0, 1, 'L'); $pdf->Ln(3); $ch2 = gd_hbar($lb2, $vl2, $CLRS, [13,148,136], 2000); $imgH2 = min(count($lb2)*9.5+8, $PH-22); insertChart($pdf, $ch2, $LM, $pdf->GetY(), $PW, $imgH2); } } } // ══════════════════════════════════════════ // PROFILER SECTIONS // ══════════════════════════════════════════ $sci=0; foreach ($SECTIONS as $sk=>$sl) { if (!isset($pdata[$sk]) || empty($pdata[$sk])) continue; $qd=$pdata[$sk]; $scl=$CLRS[$sci%count($CLRS)]; $sci++; $pc=$pcomp[$sk]??['done'=>0,'start'=>0]; $pdf->AddPage(); $pdf->SetFont('helvetica','B',24); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 11, $sl, 0, 1, 'L'); $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 6, count($qd).' question'.(count($qd)!=1?'s':'').' — '.number_format($pc['done']).' members completed this section.', 0, 1, 'L'); $pdf->Ln(3); foreach ($qd as $qid=>$qi) { $dist=$qi['dist']; $rn=$qi['n']; if(empty($dist)) continue; $lb=array_column($dist,'l'); $vl=array_map('intval',array_column($dist,'c')); $nc=count($dist); $need = ($nc<=5) ? 78 : ($nc*8.5+24); if ($pdf->GetY()+$need > (SLIDE_H - 22)) $pdf->AddPage(); $pdf->SetFont('helvetica','B',15); $pdf->SetTextColor(15,23,42); $pdf->Cell(0, 8, fmtQ($qid), 0, 1, 'L'); $pdf->SetFont('helvetica','',11); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 5, number_format($rn).' respondents', 0, 1, 'L'); $pdf->Ln(1); if ($nc<=5 && $nc>=2) { insertChart($pdf, gd_pie($lb,$vl,$CLRS,520), 35, $pdf->GetY(), 210); $pdf->Ln(68); } else { $ch=gd_hbar($lb,$vl,$CLRS,$scl,2000); $imgH=min($nc*8.5+5, 120); insertChart($pdf, $ch, $LM, $pdf->GetY(), $PW, $imgH); $pdf->Ln($imgH+3); } $pdf->Ln(3); } } // ══════════════════════════════════════════ // CONTACT PAGE // ══════════════════════════════════════════ $pdf->AddPage(); $pdf->Ln(14); $pdf->SetFillColor(5,150,105); $pdf->RoundedRect($CX-20, $pdf->GetY(), 40, 26, 5, '1111', 'F'); $pdf->SetFont('helvetica','B',22); $pdf->SetTextColor(255,255,255); $pdf->SetXY($CX-20, $pdf->GetY()+3); $pdf->Cell(40, 20, 'RR', 0, 0, 'C'); $pdf->Ln(34); $pdf->SetFont('helvetica','B',28); $pdf->SetTextColor(6,78,59); $pdf->Cell(0, 11, 'Relevant Reflex Consulting', 0, 1, 'C'); $pdf->Ln(1); $pdf->SetFont('helvetica','I',14); $pdf->SetTextColor(100,116,139); $pdf->Cell(0, 7, "India's Transparent Consumer Access Panel for Market Research", 0, 1, 'C'); $pdf->Ln(14); $ctc = [ ['Email','sridhar.mani@relevantreflex.com'], ['Phone','+91 80565 26579'], ['Web','www.relevantreflex.com'], ]; foreach ($ctc as $c) { $half = (SLIDE_W)/2; $pdf->SetFont('helvetica','',12); $pdf->SetTextColor(100,116,139); $pdf->Cell($half, 9, $c[0], 0, 0, 'R'); $pdf->SetFont('helvetica','B',13); $pdf->SetTextColor(15,23,42); $pdf->Cell($half, 9, ' '.$c[1], 0, 1, 'L'); } $pdf->Ln(14); $pdf->SetDrawColor(203,213,225); $pdf->Line($CX-40, $pdf->GetY(), $CX+40, $pdf->GetY()); $pdf->Ln(6); $pdf->SetFont('helvetica','',10); $pdf->SetTextColor(100,116,139); $pdf->MultiCell(0, 5, 'For panel inquiries, project feasibility, or partnership opportunities, please reach out to our team.', 0, 'C'); $pdf->Ln(4); $pdf->SetFont('helvetica','',9); $pdf->Cell(0, 4, 'This document was generated on '.$d['ts'].'.', 0, 1, 'C'); // ═══════════════════════════════════════════════ // 4. SERVE PDF // ═══════════════════════════════════════════════ logActivity($_SESSION['admin_id'], 'panelbook_download', 'Panel Book PDF generated'); $pdf->Output('RRC_India_Panel_Book_'.date('Y-m-d_His').'.pdf', 'D'); exit; -------------------- END OF FILE -------------------- ### FILE 27: Relevant Reflex Shop/README.md - Type: MD - Size: 8.86 KB - Path: Relevant Reflex Shop - Name: README.md ------------------------------------------------------------ # Relevant Reflex Panel Management System A comprehensive, mobile-responsive panel management system built with PHP, MySQL, and modern web technologies. Designed for fast loading on shared hosting environments like Hostinger. ## 🚀 Features - **Complete Panel Management**: Create, manage, and analyze survey panels - **User Management**: Role-based access control with admin, manager, and user roles - **Supply Chain Management**: Track inventory, suppliers, and procurement - **Demand Analytics**: Market analysis and forecasting tools - **Financial Management**: Revenue tracking and expense management - **Support System**: Built-in ticketing and help center - **Real-time Dashboard**: Live statistics and performance metrics - **Mobile Responsive**: 100% mobile-friendly design - **SEO Optimized**: Search engine friendly structure - **Fast Loading**: Optimized for shared hosting environments ## 📁 File Structure ``` relevant-reflex/ ├── index.php # Dashboard home page ├── users.php # User management ├── panel.php # Panel management ├── supply.php # Supply management ├── demand.php # Demand analysis ├── finance.php # Financial management ├── support.php # Support center ├── settings.php # System settings ├── config.php # Database configuration ├── .htaccess # Apache configuration ├── database_schema.sql # Database structure ├── robots.txt # Search engine directives ├── sitemap.xml # Site structure for SEO ├── includes/ │ ├── header.php # Site header │ ├── footer.php # SEO footer │ └── navigation.php # Navigation menu ├── assets/ │ ├── css/ │ │ ├── main.css # Main stylesheet │ │ ├── responsive.css # Mobile responsiveness │ │ └── dashboard.css # Dashboard styles │ ├── js/ │ │ ├── main.js # Core JavaScript │ │ └── dashboard.js # Dashboard functionality │ └── images/ │ └── (your images here) └── error-pages/ ├── 404.html # Page not found ├── 500.html # Server error └── maintenance.html # Maintenance mode ``` ## 🛠️ Installation Instructions ### Prerequisites - **Web Hosting**: Shared hosting account (Hostinger, cPanel, etc.) - **PHP**: Version 7.4 or higher - **MySQL**: Version 5.7 or higher - **Apache**: With mod_rewrite enabled ### Step 1: Download and Extract 1. Download all the files provided in the artifacts 2. Create a new folder named `relevant-reflex` on your computer 3. Copy all files into this folder maintaining the directory structure ### Step 2: Database Setup 1. **Login to phpMyAdmin** via your hosting control panel 2. **Create a new database**: - Database name: `relevant_reflex_db` (or your preferred name) - Collation: `utf8mb4_unicode_ci` 3. **Import the schema**: - Click on your database - Go to "Import" tab - Choose the `database_schema.sql` file - Click "Go" to execute ### Step 3: Configuration 1. **Edit config.php**: ```php define('DB_HOST', 'localhost'); define('DB_USER', 'your_db_username'); // From your hosting panel define('DB_PASS', 'your_db_password'); // From your hosting panel define('DB_NAME', 'relevant_reflex_db'); // Your database name define('SITE_URL', 'https://yourdomain.com'); // Your actual domain ``` 2. **Update site settings** in other files if needed ### Step 4: File Upload 1. **Connect via FTP/File Manager**: - Use your hosting panel's file manager or FTP client - Navigate to `public_html` directory (or your domain's root) 2. **Upload files**: - Upload all files maintaining the folder structure - Ensure permissions are set correctly: - Files: 644 - Directories: 755 - config.php: 600 (more secure) ### Step 5: Testing 1. **Visit your website**: `https://yourdomain.com` 2. **Default admin login**: - Username: `admin` - Email: `admin@relevantreflex.com` - Password: `admin123` - **⚠️ Change this immediately after first login!** 3. **Test all features**: - Dashboard loading - User management - Panel creation - Mobile responsiveness ### Step 6: Security Hardening 1. **Change default admin password** 2. **Update config.php** with strong database credentials 3. **Enable SSL certificate** (usually free with hosting) 4. **Uncomment HTTPS redirect** in .htaccess: ```apache RewriteCond %{HTTPS} off RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301] ``` ## 🎨 Customization ### Changing Colors and Theme All colors are centralized in `assets/css/main.css` at the top: ```css :root { --primary-color: #0066cc; /* Change main brand color */ --primary-hover: #0052a3; /* Hover state */ --success-color: #28a745; /* Success messages */ --warning-color: #ffc107; /* Warnings */ --danger-color: #dc3545; /* Errors */ /* ... more color variables */ } ``` Simply update these values to match your brand colors. ### Adding Your Logo 1. **Replace the RR logo**: - Update the `.logo` content in `includes/navigation.php` - Or replace with an image: `Your Logo` 2. **Update favicon**: - Add your `favicon.ico` to the root directory - Update the reference in `includes/header.php` ### Custom Styling - **Main styles**: `assets/css/main.css` - **Mobile styles**: `assets/css/responsive.css` - **Dashboard styles**: `assets/css/dashboard.css` ## 📱 Mobile Optimization The system is built mobile-first with: - **Responsive Grid System**: Adapts to all screen sizes - **Touch-Friendly Interface**: 44px minimum touch targets - **Optimized Navigation**: Hamburger menu on mobile - **Fast Loading**: Optimized assets and caching - **Progressive Enhancement**: Works without JavaScript ## 🔧 Maintenance ### Regular Tasks 1. **Database Backups**: Weekly automated backups recommended 2. **Update Dependencies**: Keep PHP and MySQL updated 3. **Monitor Performance**: Check load times and optimize 4. **Security Updates**: Regular security audits 5. **Content Updates**: Keep information current ### Performance Optimization 1. **Enable Gzip Compression** (included in .htaccess) 2. **Optimize Images**: Use WebP format when possible 3. **Monitor Database**: Run `OPTIMIZE TABLE` monthly 4. **Cache Headers**: Properly configured in .htaccess 5. **CDN Integration**: Consider using a CDN for static assets ## 🔐 Security Features - **SQL Injection Protection**: PDO prepared statements - **XSS Prevention**: Input sanitization and CSP headers - **CSRF Protection**: Session-based token validation - **Secure Headers**: Comprehensive security headers - **File Upload Security**: Restricted file types and locations - **Access Control**: Role-based permissions ## 🆘 Troubleshooting ### Common Issues **1. Database Connection Error** ``` Solution: Check config.php credentials and database server status ``` **2. Page Not Found (404)** ``` Solution: Verify .htaccess file is uploaded and mod_rewrite is enabled ``` **3. Slow Loading** ``` Solution: Enable compression, check hosting performance, optimize images ``` **4. Mobile Display Issues** ``` Solution: Clear browser cache, check responsive.css is loaded ``` **5. JavaScript Not Working** ``` Solution: Check browser console for errors, verify JS files are accessible ``` ### Getting Help 1. **Check Error Logs**: In your hosting control panel 2. **Browser Console**: F12 to check for JavaScript errors 3. **PHP Error Display**: Temporarily enable in config.php for debugging 4. **Hosting Support**: Contact your hosting provider for server issues ## 📞 Support For technical support and customization services: - **Email**: support@relevantreflex.com - **Documentation**: Check inline comments in code files - **Updates**: Monitor for system updates and security patches ## 📝 License This system is proprietary software developed for Relevant Reflex. All rights reserved. ## 🚀 Quick Start Checklist - [ ] Create database in phpMyAdmin - [ ] Import database_schema.sql - [ ] Update config.php with database credentials - [ ] Upload all files to web server - [ ] Set correct file permissions - [ ] Test login with admin/admin123 - [ ] Change default admin password - [ ] Customize colors and branding - [ ] Enable SSL and HTTPS redirect - [ ] Test all functionality - [ ] Setup regular backups ## Version Information - **Version**: 1.0.0 - **Release Date**: September 2025 - **PHP Compatibility**: 7.4+ - **MySQL Compatibility**: 5.7+ - **Browser Support**: All modern browsers, IE11+ --- **Important**: Always backup your database and files before making changes or updates. -------------------- END OF FILE -------------------- ### FILE 28: Relevant Reflex Shop/robots.txt - Type: TXT - Size: 2.16 KB - Path: Relevant Reflex Shop - Name: robots.txt ------------------------------------------------------------ # Relevant Reflex Panel Management System # Robots.txt file for search engine optimization User-agent: * # Allow access to main pages Allow: / Allow: /index.php Allow: /users.php Allow: /panel.php Allow: /supply.php Allow: /demand.php Allow: /finance.php Allow: /support.php Allow: /settings.php # Allow access to static assets Allow: /assets/css/ Allow: /assets/js/ Allow: /assets/images/ # Disallow sensitive files and directories Disallow: /config.php Disallow: /database_schema.sql Disallow: /.htaccess Disallow: /includes/ Disallow: /logs/ Disallow: /backups/ Disallow: /temp/ Disallow: /cache/ Disallow: /admin/ Disallow: /api/ Disallow: /private/ # Disallow URL parameters that might create duplicate content Disallow: /*?* Disallow: /*&* Disallow: /*/search?* Disallow: /*/filter?* # Disallow error pages Disallow: /error-* Disallow: /404.html Disallow: /500.html Disallow: /maintenance.html # Block access to development and testing files Disallow: /test/ Disallow: /dev/ Disallow: /staging/ Disallow: /*.bak Disallow: /*.tmp Disallow: /*.log # Block common exploits and security probes Disallow: /wp-admin/ Disallow: /wordpress/ Disallow: /wp-content/ Disallow: /admin.php Disallow: /administrator/ Disallow: /phpmyadmin/ Disallow: /phpMyAdmin/ # Block unwanted file types Disallow: /*.sql$ Disallow: /*.zip$ Disallow: /*.tar.gz$ Disallow: /*.bak$ Disallow: /*.conf$ Disallow: /*.ini$ # Sitemap location Sitemap: https://yourdomain.com/sitemap.xml # Crawl delay for respectful crawling (optional) # Crawl-delay: 1 # Specific rules for different bots (optional) # Google Bot - allow everything we want indexed User-agent: Googlebot Allow: / Disallow: /config.php Disallow: /includes/ Disallow: /*?* # Bing Bot User-agent: Bingbot Allow: / Disallow: /config.php Disallow: /includes/ # Block aggressive bots that might overload the server User-agent: AhrefsBot Disallow: / User-agent: MJ12bot Disallow: / User-agent: SemrushBot Disallow: / User-agent: DotBot Disallow: / # Allow social media bots for link previews User-agent: facebookexternalhit Allow: / User-agent: Twitterbot Allow: / User-agent: LinkedInBot Allow: / # Note: Update "yourdomain.com" with your actual domain name -------------------- END OF FILE -------------------- ### FILE 29: Relevant Reflex Shop/settings.php - Type: PHP - Size: 28.63 KB - Path: Relevant Reflex Shop - Name: settings.php ------------------------------------------------------------ exec(" CREATE TABLE IF NOT EXISTS company_settings ( id INT AUTO_INCREMENT PRIMARY KEY, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) "); } catch (Exception $e) { // Table might already exist } // Handle form submission if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['save_settings'])) { try { $fields = [ 'company_name', 'company_address', 'company_city', 'company_state', 'company_country', 'company_pincode', 'company_phone', 'company_email', 'company_website', 'tax_gst', 'tax_pan', 'tax_cin', 'tax_sac_code', 'bank_name', 'bank_account_name', 'bank_account_number', 'bank_ifsc', 'bank_branch', 'bank_swift', 'invoice_prefix', 'invoice_terms', 'invoice_notes', 'invoice_footer' ]; $stmt = $pdo->prepare(" INSERT INTO company_settings (setting_key, setting_value, updated_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value), updated_at = NOW() "); foreach ($fields as $field) { $value = trim($_POST[$field] ?? ''); $stmt->execute([$field, $value]); } $success = 'Company settings saved successfully!'; $mode = 'view'; logActivity($_SESSION['admin_id'], 'update', 'Updated company invoicing settings', 'settings', null); } catch (Exception $e) { $error = 'Error saving settings: ' . $e->getMessage(); $mode = 'edit'; } } // Load current settings $settings = []; try { $result = $pdo->query("SELECT setting_key, setting_value FROM company_settings"); while ($row = $result->fetch()) { $settings[$row['setting_key']] = $row['setting_value']; } } catch (Exception $e) {} function getSetting($key, $default = '') { global $settings; return $settings[$key] ?? $default; } $hasData = !empty(array_filter($settings)); include 'includes/header.php'; ?>

Company Settings

Manage your company details used in client invoicing

📋

No Company Details Saved Yet

Add your company information for invoicing by switching to Edit mode.

✏ Add Company Details

🏢 Company Information

Basic company details that appear on invoices and official documents.

— Not set

📋 Tax & Registration

Tax identification numbers and registration details.

💳 Bank Details

Banking information displayed on invoices for client payments.

📄 Invoice Preferences

Default text and formatting for generated invoices.

🏢 Company Information

Basic company details that appear on invoices and official documents.

📋 Tax & Registration

Tax identification numbers and registration details for invoicing compliance.

GST Identification Number or equivalent Tax ID
Permanent Account Number
Corporate Identification Number
Service Accounting Code for your services

💳 Bank Details

Banking information displayed on invoices for client payments.

Required for international payments

📄 Invoice Preferences

Default text and formatting preferences for generated invoices.

e.g. RR-INV-001, RR-INV-002 ...
Cancel
-------------------- END OF FILE -------------------- ### FILE 30: Relevant Reflex Shop/sitemap.xml - Type: XML - Size: 5.24 KB - Path: Relevant Reflex Shop - Name: sitemap.xml ------------------------------------------------------------ https://yourdomain.com/ 2025-09-03 daily 1.0 https://yourdomain.com/users.php 2025-09-03 weekly 0.8 https://yourdomain.com/panel.php 2025-09-03 weekly 0.9 https://yourdomain.com/supply.php 2025-09-03 weekly 0.7 https://yourdomain.com/demand.php 2025-09-03 monthly 0.7 https://yourdomain.com/finance.php 2025-09-03 weekly 0.8 https://yourdomain.com/support.php 2025-09-03 monthly 0.6 https://yourdomain.com/settings.php 2025-09-03 monthly 0.5 -------------------- END OF FILE -------------------- ### FILE 31: Relevant Reflex Shop/superlog-generate.php - Type: PHP - Size: 2.8 KB - Path: Relevant Reflex Shop - Name: superlog-generate.php ------------------------------------------------------------ exec("CREATE TABLE IF NOT EXISTS superlog_tokens ( id INT AUTO_INCREMENT PRIMARY KEY, admin_id INT NOT NULL, admin_username VARCHAR(100) NOT NULL, admin_name VARCHAR(200) NOT NULL, token VARCHAR(128) NOT NULL, ip_address VARCHAR(45), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, expires_at DATETIME NOT NULL, used TINYINT(1) DEFAULT 0, used_at DATETIME NULL, UNIQUE KEY idx_token (token), INDEX idx_expires (expires_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); // Clean up expired / old used tokens $pdo->exec("DELETE FROM superlog_tokens WHERE expires_at < NOW() OR (used = 1 AND created_at < DATE_SUB(NOW(), INTERVAL 24 HOUR))"); // Rate limit: max 10 tokens per admin per hour $stmt = $pdo->prepare("SELECT COUNT(*) FROM superlog_tokens WHERE admin_id = ? AND created_at > DATE_SUB(NOW(), INTERVAL 1 HOUR)"); $stmt->execute([$_SESSION['admin_id']]); if ((int)$stmt->fetchColumn() >= 10) { die('Rate limit exceeded. Please wait before generating another Super Log token.'); } // Generate cryptographically secure token $token = bin2hex(random_bytes(64)); // 128 chars // Store token with 5-minute expiry $stmt = $pdo->prepare(" INSERT INTO superlog_tokens (admin_id, admin_username, admin_name, token, ip_address, expires_at) VALUES (?, ?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 5 MINUTE)) "); $stmt->execute([ $_SESSION['admin_id'], $_SESSION['admin_username'], $_SESSION['admin_name'] ?? $_SESSION['admin_username'], $token, $_SERVER['REMOTE_ADDR'] ?? '' ]); // Log the superlog initiation in admin activity logActivity($_SESSION['admin_id'], 'superlog_init', 'Super Log session initiated from ' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown')); // Redirect to superlog page on relevantreflex.com header('Location: https://relevantreflex.com/superlog/?token=' . urlencode($token)); exit; } catch (Exception $e) { error_log("Superlog token generation error: " . $e->getMessage()); die('Error generating Super Log token. Please try again.'); } -------------------- END OF FILE -------------------- ### FILE 32: Relevant Reflex Shop/supply.php - Type: PHP - Size: 21.71 KB - Path: Relevant Reflex Shop - Name: supply.php ------------------------------------------------------------ prepare("SELECT affiliate_code FROM affiliates WHERE id = ?"); $stmt->execute([$delete_id]); $affiliate_code = $stmt->fetchColumn(); // Delete affiliate (cascade will delete attachments and signups) $stmt = $pdo->prepare("DELETE FROM affiliates WHERE id = ?"); $stmt->execute([$delete_id]); logActivity($_SESSION['admin_id'], 'delete_affiliate', "Deleted affiliate: $affiliate_code", 'affiliate', $delete_id); $success = 'Affiliate deleted successfully!'; } catch (Exception $e) { $error = 'An error occurred while deleting. Please try again.'; error_log("Delete affiliate error: " . $e->getMessage()); } } } } // Fetch all affiliates try { $pdo = getDBConnection(); $stmt = $pdo->query("SELECT * FROM affiliates ORDER BY created_at DESC"); $affiliates = $stmt->fetchAll(); } catch (Exception $e) { $affiliates = []; error_log("Fetch affiliates error: " . $e->getMessage()); } // Calculate statistics $total_affiliates = count($affiliates); $active_affiliates = count(array_filter($affiliates, fn($a) => $a['status'] === 'active')); $total_hits = array_sum(array_column($affiliates, 'total_signups')); // total_signups = total clicks/hits $verified_signups = array_sum(array_column($affiliates, 'total_verified_signups')); $company_affiliates = count(array_filter($affiliates, fn($a) => $a['type'] === 'company')); $individual_affiliates = count(array_filter($affiliates, fn($a) => $a['type'] === 'individual')); // Count not verified: registered (signup_completed=1) but email NOT verified $not_verified_count = 0; try { $stmt = $pdo->query(" SELECT COUNT(*) FROM affiliate_signups WHERE signup_completed = 1 AND email_verified = 0 "); $not_verified_count = (int)$stmt->fetchColumn(); } catch (Exception $e) { error_log("Not verified count error: " . $e->getMessage()); } include 'includes/header.php'; ?>
Total Affiliates
Active Affiliates
Total Hits
Not Verified
Verified Signups
Companies
Individuals
ID Affiliate Code Company/Name Type In-charge Location Contact Hits Verified Status Actions
No affiliates found. Click "Add New Affiliate" to create one.
#
-
👁
-------------------- END OF FILE -------------------- ### FILE 33: Relevant Reflex Shop/support.php - Type: PHP - Size: 2.21 KB - Path: Relevant Reflex Shop - Name: support.php ------------------------------------------------------------
🛠

Support Center

Our support portal is available on the main Relevant Reflex website. Submit tickets, track requests, and access help resources all in one place.

Open Support Portal →

Opens in a new window at relevantreflex.com

-------------------- END OF FILE -------------------- ### FILE 34: Relevant Reflex Shop/users.php - Type: PHP - Size: 23.18 KB - Path: Relevant Reflex Shop - Name: users.php ------------------------------------------------------------ prepare("INSERT INTO admin_users (username, email, password, full_name, role, created_by) VALUES (?, ?, ?, ?, ?, ?)"); $stmt->execute([$username, $email, $hashedPassword, $full_name, $role, $_SESSION['admin_id']]); logActivity($_SESSION['admin_id'], 'create_admin_user', "Created admin user: $username", 'admin_user', $pdo->lastInsertId()); $success = 'Admin user created successfully!'; } catch (PDOException $e) { if ($e->getCode() == 23000) { $error = 'Username or email already exists'; } else { $error = 'An error occurred. Please try again.'; error_log("Create user error: " . $e->getMessage()); } } } } if ($_POST['action'] === 'toggle_status') { $user_id = intval($_POST['user_id'] ?? 0); $new_status = $_POST['new_status'] ?? 'active'; try { $pdo = getDBConnection(); $stmt = $pdo->prepare("UPDATE admin_users SET status = ? WHERE id = ?"); $stmt->execute([$new_status, $user_id]); logActivity($_SESSION['admin_id'], 'update_admin_user', "Changed user status to: $new_status", 'admin_user', $user_id); $success = 'User status updated successfully!'; } catch (Exception $e) { $error = 'An error occurred. Please try again.'; error_log("Status update error: " . $e->getMessage()); } } if ($_POST['action'] === 'delete_user' && isAdmin()) { $user_id = intval($_POST['user_id'] ?? 0); if ($user_id > 0 && $user_id != $_SESSION['admin_id']) { try { $pdo = getDBConnection(); // Get username for logging $stmt = $pdo->prepare("SELECT username FROM admin_users WHERE id = ?"); $stmt->execute([$user_id]); $username = $stmt->fetchColumn(); // Delete user $stmt = $pdo->prepare("DELETE FROM admin_users WHERE id = ?"); $stmt->execute([$user_id]); logActivity($_SESSION['admin_id'], 'delete_admin_user', "Deleted admin user: $username", 'admin_user', $user_id); $success = 'Admin user deleted successfully!'; } catch (Exception $e) { $error = 'An error occurred while deleting. Please try again.'; error_log("Delete user error: " . $e->getMessage()); } } } } // Fetch all users try { $pdo = getDBConnection(); $stmt = $pdo->query("SELECT * FROM admin_users ORDER BY created_at DESC"); $users = $stmt->fetchAll(); } catch (Exception $e) { $users = []; error_log("Fetch users error: " . $e->getMessage()); } include 'includes/header.php'; ?>
Total Users
$u['status'] === 'active')); ?>
Active Users
$u['role'] === 'admin')); ?>
Admins
$u['role'] === 'manager')); ?>
Managers
ID User Details Email Role Status Last Login Actions
#
Never
Current User
-------------------- END OF FILE -------------------- ### FILE 35: Relevant Reflex Shop/assets/css/dashboard.css - Type: CSS - Size: 28.48 KB - Path: Relevant Reflex Shop/assets/css - Name: dashboard.css ------------------------------------------------------------ /* Dashboard Specific Styles for Relevant Reflex */ /* Dashboard Container */ .dashboard-container { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); } /* Enhanced Stats Cards for Dashboard */ .dashboard-content .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--spacing-xl); margin-bottom: var(--spacing-2xl); } .dashboard-content .stat-card { background: linear-gradient(135deg, var(--white) 0%, #f8fafc 100%); border: 1px solid var(--gray-100); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); display: flex; align-items: center; gap: var(--spacing-xl); transition: all var(--transition-normal); position: relative; overflow: hidden; } .dashboard-content .stat-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: linear-gradient(90deg, var(--primary-color), var(--primary-dark)); } .dashboard-content .stat-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--primary-color); } .dashboard-content .stat-icon { font-size: 3rem; width: 70px; height: 70px; background: linear-gradient(135deg, var(--primary-light), rgba(5, 150, 105, 0.1)); border: 2px solid var(--primary-color); border-radius: var(--border-radius-2xl); display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; } .dashboard-content .stat-icon::after { content: ''; position: absolute; inset: 2px; background: linear-gradient(135deg, transparent, rgba(255, 255, 255, 0.1)); border-radius: calc(var(--border-radius-2xl) - 2px); pointer-events: none; } .dashboard-content .stat-content h3 { font-size: var(--font-size-4xl); margin: 0; color: var(--primary-color); font-weight: var(--font-weight-bold); line-height: 1; } .dashboard-content .stat-content p { margin: var(--spacing-sm) 0 0; color: var(--gray-600); font-weight: var(--font-weight-semibold); font-size: var(--font-size-base); } .stat-change { display: inline-flex; align-items: center; gap: var(--spacing-xs); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); margin-top: var(--spacing-sm); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-xl); } .stat-change.positive { background: var(--success-light); color: var(--success-color); } .stat-change.negative { background: var(--danger-light); color: var(--danger-color); } .stat-change.neutral { background: var(--gray-100); color: var(--gray-600); } .stat-change::before { content: '↗'; font-size: var(--font-size-sm); } .stat-change.negative::before { content: '↘'; } .stat-change.neutral::before { content: '→'; } /* Dashboard Content Grid */ .dashboard-content { margin-top: var(--spacing-2xl); } .content-grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--spacing-2xl); margin-bottom: var(--spacing-2xl); } /* Chart Section */ .chart-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xl); padding-bottom: var(--spacing-lg); border-bottom: 1px solid var(--gray-200); } .section-header h2 { margin: 0; color: var(--dark-color); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); } .chart-controls select { padding: var(--spacing-sm) var(--spacing-md); border: 1px solid var(--gray-300); border-radius: var(--border-radius-lg); background: var(--white); font-size: var(--font-size-sm); cursor: pointer; } .chart-container { position: relative; height: 300px; background: var(--gray-50); border-radius: var(--border-radius-lg); display: flex; align-items: center; justify-content: center; border: 1px dashed var(--gray-300); } .chart-placeholder { color: var(--gray-500); font-style: italic; text-align: center; } .chart-legend { display: flex; gap: var(--spacing-lg); margin-top: var(--spacing-lg); justify-content: center; } .legend-item { display: flex; align-items: center; gap: var(--spacing-sm); font-size: var(--font-size-sm); } .legend-color { width: 16px; height: 16px; border-radius: var(--border-radius-sm); } /* Recent Activity */ .recent-activity { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .view-all { color: var(--primary-color); text-decoration: none; font-weight: var(--font-weight-medium); font-size: var(--font-size-sm); transition: color var(--transition-normal); } .view-all:hover { color: var(--primary-hover); text-decoration: underline; } .activity-list { display: flex; flex-direction: column; gap: var(--spacing-md); } .activity-item { display: flex; flex-direction: column; gap: var(--spacing-sm); padding: var(--spacing-lg); background: linear-gradient(135deg, var(--gray-50), #f1f5f9); border-radius: var(--border-radius-lg); border-left: 4px solid var(--primary-color); transition: all var(--transition-normal); position: relative; } .activity-item:hover { background: linear-gradient(135deg, var(--primary-light), rgba(5, 150, 105, 0.05)); transform: translateX(4px); box-shadow: var(--shadow-sm); } .activity-time { font-size: var(--font-size-xs); color: var(--gray-500); font-weight: var(--font-weight-medium); text-transform: uppercase; letter-spacing: 0.5px; } .activity-text { color: var(--dark-color); font-weight: var(--font-weight-normal); line-height: 1.5; } .activity-footer { margin-top: var(--spacing-lg); text-align: center; padding-top: var(--spacing-lg); border-top: 1px solid var(--gray-200); } /* Quick Actions Section */ .quick-actions-section { margin-top: var(--spacing-2xl); background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .quick-actions-section h2 { margin-bottom: var(--spacing-xl); color: var(--dark-color); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); text-align: center; } .quick-actions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); } .quick-action-card { background: linear-gradient(135deg, var(--white) 0%, var(--gray-50) 100%); border: 1px solid var(--gray-200); border-radius: var(--border-radius-xl); padding: var(--spacing-xl); text-align: center; cursor: pointer; transition: all var(--transition-normal); text-decoration: none; color: inherit; } .quick-action-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--primary-color); background: linear-gradient(135deg, var(--primary-light) 0%, rgba(5, 150, 105, 0.05) 100%); } .action-icon { font-size: 2.5rem; margin-bottom: var(--spacing-lg); display: block; } .quick-action-card h3 { margin: 0 0 var(--spacing-md); color: var(--dark-color); font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); } .quick-action-card p { margin: 0; color: var(--gray-600); font-size: var(--font-size-sm); line-height: 1.5; } /* Overview Cards for Other Pages */ .overview-grid, .user-stats .stats-row, .supply-overview .stats-grid, .panel-overview .overview-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--spacing-lg); margin-bottom: var(--spacing-2xl); } .overview-card, .stat-item { background: var(--white); border-radius: var(--border-radius-xl); padding: var(--spacing-lg); text-align: center; box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); transition: all var(--transition-normal); } .overview-card:hover, .stat-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .overview-icon { font-size: 2rem; margin-bottom: var(--spacing-md); display: block; } .overview-content h3, .stat-number { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); color: var(--primary-color); margin: 0 0 var(--spacing-sm); } .overview-content p, .stat-label { color: var(--gray-600); font-weight: var(--font-weight-medium); margin: 0; } /* Login Information Styling */ .login-info { display: flex; flex-direction: column; gap: 2px; } .login-date { font-weight: var(--font-weight-medium); color: var(--dark-color); font-size: var(--font-size-sm); } .login-time { font-size: var(--font-size-xs); color: var(--gray-500); } .never-logged { font-style: italic; color: var(--gray-400); font-size: var(--font-size-sm); } /* Response Information */ .response-info { display: flex; flex-direction: column; gap: var(--spacing-xs); } .response-info strong { color: var(--primary-color); font-size: var(--font-size-lg); } .response-info small { color: var(--gray-500); font-size: var(--font-size-xs); } /* Date Information */ .date-info { display: flex; flex-direction: column; gap: var(--spacing-xs); } .end-date { font-weight: var(--font-weight-medium); color: var(--dark-color); } .days-left { font-size: var(--font-size-xs); color: var(--gray-500); } /* Quantity Badge */ .quantity-badge { background: var(--primary-light); color: var(--primary-color); padding: var(--spacing-xs) var(--spacing-md); border-radius: var(--border-radius-xl); font-weight: var(--font-weight-bold); font-size: var(--font-size-sm); } /* Financial Cards */ .finance-card { position: relative; overflow: hidden; } .finance-card.revenue-card::before { background: linear-gradient(90deg, var(--success-color), #20c997); } .finance-card.expense-card::before { background: linear-gradient(90deg, var(--danger-color), #e74c3c); } .finance-card.profit-card::before { background: linear-gradient(90deg, var(--primary-color), var(--primary-dark)); } .stat-footer { font-size: var(--font-size-xs); color: var(--gray-500); margin-top: var(--spacing-sm); text-align: center; font-weight: var(--font-weight-medium); } /* Trend Indicators */ .trend { display: inline-flex; align-items: center; gap: var(--spacing-xs); font-size: var(--font-size-sm); font-weight: var(--font-weight-bold); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-xl); margin-top: var(--spacing-sm); } .trend-up { background: var(--success-light); color: var(--success-color); } .trend-down { background: var(--danger-light); color: var(--danger-color); } .trend-up::before { content: '↗'; } .trend-down::before { content: '↘'; } /* Demand Metrics */ .demand-metrics { margin-bottom: var(--spacing-2xl); } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--spacing-lg); } .metric-card { background: var(--white); border-radius: var(--border-radius-xl); padding: var(--spacing-xl); text-align: center; box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); transition: all var(--transition-normal); } .metric-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .metric-icon { font-size: 2.5rem; margin-bottom: var(--spacing-lg); display: block; } .metric-value { font-size: var(--font-size-4xl); font-weight: var(--font-weight-bold); color: var(--primary-color); line-height: 1; margin-bottom: var(--spacing-sm); } .metric-label { color: var(--gray-600); font-weight: var(--font-weight-medium); margin-bottom: var(--spacing-sm); } .metric-change { font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); padding: var(--spacing-xs) var(--spacing-sm); border-radius: var(--border-radius-xl); } /* Category Performance */ .category-performance { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .category-list { display: flex; flex-direction: column; gap: var(--spacing-lg); } .category-item { border: 1px solid var(--gray-200); border-radius: var(--border-radius-lg); padding: var(--spacing-lg); transition: all var(--transition-normal); } .category-item:hover { border-color: var(--primary-color); background: var(--primary-light); } .category-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-md); } .category-header h4 { margin: 0; color: var(--dark-color); font-weight: var(--font-weight-semibold); } .category-metrics { display: flex; justify-content: space-between; align-items: center; } .demand-level { padding: var(--spacing-xs) var(--spacing-md); border-radius: var(--border-radius-xl); font-size: var(--font-size-sm); font-weight: var(--font-weight-bold); } .demand-high { background: var(--success-light); color: var(--success-color); } .demand-medium { background: var(--warning-light); color: #856404; } .demand-low { background: var(--gray-200); color: var(--gray-600); } .category-growth { font-weight: var(--font-weight-bold); } .category-growth.positive { color: var(--success-color); } .category-growth.negative { color: var(--danger-color); } /* Insights Sections */ .insights-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); margin-top: var(--spacing-2xl); } .insights-section h2 { margin-bottom: var(--spacing-xl); color: var(--dark-color); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); text-align: center; } .insights-grid, .insights-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--spacing-lg); } .insight-card { background: linear-gradient(135deg, var(--gray-50) 0%, #f8fafc 100%); border: 1px solid var(--gray-200); border-radius: var(--border-radius-xl); padding: var(--spacing-xl); transition: all var(--transition-normal); } .insight-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--primary-color); } .insight-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-lg); } .insight-number { width: 30px; height: 30px; background: var(--primary-color); color: var(--white); border-radius: var(--border-radius-full); display: flex; align-items: center; justify-content: center; font-weight: var(--font-weight-bold); font-size: var(--font-size-sm); } .insight-impact { padding: var(--spacing-xs) var(--spacing-md); border-radius: var(--border-radius-xl); font-size: var(--font-size-xs); font-weight: var(--font-weight-bold); text-transform: uppercase; letter-spacing: 0.5px; } .impact-high { background: var(--danger-light); color: var(--danger-color); } .impact-medium { background: var(--warning-light); color: #856404; } .impact-low { background: var(--info-light); color: var(--info-color); } .insight-content h4 { margin: 0 0 var(--spacing-md); color: var(--dark-color); font-weight: var(--font-weight-semibold); line-height: 1.3; } .insight-content p { margin: 0 0 var(--spacing-md); color: var(--gray-600); line-height: 1.6; } .recommended-action { margin: 0 !important; padding: var(--spacing-md); background: var(--primary-light); border-radius: var(--border-radius-lg); border-left: 4px solid var(--primary-color); font-size: var(--font-size-sm); } .recommended-action strong { color: var(--primary-color); } /* Transaction Items */ .transactions-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .transactions-list { display: flex; flex-direction: column; gap: var(--spacing-md); } .transaction-item { display: flex; align-items: center; gap: var(--spacing-lg); padding: var(--spacing-lg); border-radius: var(--border-radius-lg); border: 1px solid var(--gray-200); transition: all var(--transition-normal); } .transaction-item:hover { border-color: var(--primary-color); background: var(--primary-light); } .transaction-item.revenue { border-left: 4px solid var(--success-color); } .transaction-item.expense { border-left: 4px solid var(--danger-color); } .transaction-icon { font-size: 1.5rem; width: 40px; height: 40px; border-radius: var(--border-radius-full); display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .transaction-item.revenue .transaction-icon { background: var(--success-light); } .transaction-item.expense .transaction-icon { background: var(--danger-light); } .transaction-details { flex: 1; } .transaction-description { font-weight: var(--font-weight-medium); color: var(--dark-color); margin-bottom: var(--spacing-xs); } .transaction-meta { display: flex; gap: var(--spacing-lg); font-size: var(--font-size-sm); color: var(--gray-500); } .transaction-amount { font-size: var(--font-size-lg); font-weight: var(--font-weight-bold); } .transaction-amount.positive { color: var(--success-color); } .transaction-amount.negative { color: var(--danger-color); } /* Support Specific Styles */ .support-overview { margin-bottom: var(--spacing-2xl); } .support-card .stat-icon { background: linear-gradient(135deg, var(--info-light), rgba(23, 162, 184, 0.1)); border: 2px solid var(--info-color); } .help-resources { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); } .help-item { display: flex; align-items: center; gap: var(--spacing-lg); padding: var(--spacing-xl); background: var(--white); border: 1px solid var(--gray-200); border-radius: var(--border-radius-xl); text-decoration: none; color: inherit; transition: all var(--transition-normal); } .help-item:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--primary-color); background: var(--primary-light); } .help-icon { font-size: 2rem; width: 50px; height: 50px; background: var(--primary-light); border-radius: var(--border-radius-xl); display: flex; align-items: center; justify-content: center; flex-shrink: 0; } .help-content h4 { margin: 0 0 var(--spacing-xs); color: var(--dark-color); font-weight: var(--font-weight-semibold); } .help-content p { margin: 0; color: var(--gray-600); font-size: var(--font-size-sm); } /* Tickets Section */ .tickets-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); margin-top: var(--spacing-2xl); } .tickets-container { display: flex; flex-direction: column; gap: var(--spacing-lg); } .ticket-item { border: 1px solid var(--gray-200); border-radius: var(--border-radius-lg); padding: var(--spacing-lg); transition: all var(--transition-normal); } .ticket-item:hover { border-color: var(--primary-color); background: var(--primary-light); } .ticket-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-md); } .ticket-id { font-family: var(--font-family-mono); font-weight: var(--font-weight-bold); color: var(--primary-color); background: var(--primary-light); padding: var(--spacing-xs) var(--spacing-md); border-radius: var(--border-radius-lg); } .ticket-content h4 { margin: 0 0 var(--spacing-md); color: var(--dark-color); font-weight: var(--font-weight-medium); } .ticket-meta { display: flex; gap: var(--spacing-lg); align-items: center; margin-bottom: var(--spacing-md); } .ticket-date { font-size: var(--font-size-sm); color: var(--gray-500); } /* FAQ Section */ .faq-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); margin-top: var(--spacing-2xl); } .faq-container { display: flex; flex-direction: column; gap: var(--spacing-md); } .faq-item { display: flex; justify-content: space-between; align-items: center; padding: var(--spacing-lg); border: 1px solid var(--gray-200); border-radius: var(--border-radius-lg); transition: all var(--transition-normal); } .faq-item:hover { border-color: var(--primary-color); background: var(--primary-light); } .faq-question h4 { margin: 0 0 var(--spacing-xs); color: var(--dark-color); font-weight: var(--font-weight-medium); } .faq-category { font-size: var(--font-size-xs); color: var(--primary-color); font-weight: var(--font-weight-bold); text-transform: uppercase; letter-spacing: 0.5px; } .faq-toggle { background: var(--primary-color); color: var(--white); border: none; padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--border-radius-lg); font-size: var(--font-size-sm); font-weight: var(--font-weight-medium); cursor: pointer; transition: all var(--transition-normal); flex-shrink: 0; } .faq-toggle:hover { background: var(--primary-hover); transform: scale(1.05); } /* Settings Specific Styles */ .settings-section { background: var(--white); } .settings-section h2 { margin-bottom: var(--spacing-xl); color: var(--dark-color); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); padding-bottom: var(--spacing-lg); border-bottom: 2px solid var(--primary-color); } .settings-section h3 { margin: var(--spacing-xl) 0 var(--spacing-lg); color: var(--gray-700); font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); } .notification-group { background: var(--gray-50); border-radius: var(--border-radius-lg); padding: var(--spacing-lg); margin-bottom: var(--spacing-lg); } /* Color Picker */ .color-picker-group { display: flex; gap: var(--spacing-md); align-items: center; } .color-picker-group input[type="color"] { width: 60px; height: 40px; border-radius: var(--border-radius); border: 1px solid var(--gray-300); cursor: pointer; } .color-hex { flex: 1; background: var(--gray-100); font-family: var(--font-family-mono); } /* Integration Cards */ .integrations-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--spacing-lg); } .integration-card { background: var(--white); border: 1px solid var(--gray-200); border-radius: var(--border-radius-xl); padding: var(--spacing-xl); transition: all var(--transition-normal); } .integration-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); border-color: var(--primary-color); } .integration-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--spacing-lg); } .integration-header h4 { margin: 0; color: var(--dark-color); font-weight: var(--font-weight-semibold); } .integration-icon { font-size: 1.5rem; width: 40px; height: 40px; background: var(--primary-light); border-radius: var(--border-radius-lg); display: flex; align-items: center; justify-content: center; margin-bottom: var(--spacing-md); } /* Account Management */ .account-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); margin-top: var(--spacing-2xl); } .account-section h2 { margin-bottom: var(--spacing-xl); color: var(--dark-color); font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); } .account-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--spacing-lg); } .account-card { background: var(--gray-50); border: 1px solid var(--gray-200); border-radius: var(--border-radius-xl); padding: var(--spacing-xl); transition: all var(--transition-normal); text-align: center; } .account-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .account-card.danger-card { border-color: var(--danger-color); background: var(--danger-light); } .account-card.danger-card:hover { background: #f8d7da; } .account-icon { font-size: 2rem; margin-bottom: var(--spacing-lg); display: block; } .account-content h4 { margin: 0 0 var(--spacing-md); color: var(--dark-color); font-weight: var(--font-weight-semibold); } .account-content p { margin: 0 0 var(--spacing-lg); color: var(--gray-600); font-size: var(--font-size-sm); line-height: 1.5; } /* Password Strength Indicator */ .password-strength { margin-top: var(--spacing-md); } .strength-bar { height: 4px; background: var(--gray-200); border-radius: var(--border-radius-sm); overflow: hidden; margin-bottom: var(--spacing-sm); } .strength-fill { height: 100%; border-radius: var(--border-radius-sm); transition: all var(--transition-normal); } .strength-fill.strength-weak { background: var(--danger-color); width: 25%; } .strength-fill.strength-fair { background: var(--warning-color); width: 50%; } .strength-fill.strength-good { background: var(--info-color); width: 75%; } .strength-fill.strength-strong { background: var(--success-color); width: 100%; } .strength-text { font-size: var(--font-size-xs); color: var(--gray-500); } /* Summary Stats */ .summary-section { background: var(--white); border-radius: var(--border-radius-2xl); padding: var(--spacing-xl); box-shadow: var(--shadow-sm); border: 1px solid var(--gray-100); } .summary-stats { display: flex; flex-direction: column; gap: var(--spacing-lg); } .summary-item { text-align: center; padding: var(--spacing-lg); background: var(--gray-50); border-radius: var(--border-radius-lg); border: 1px solid var(--gray-200); } .summary-item h3 { font-size: var(--font-size-2xl); font-weight: var(--font-weight-bold); color: var(--primary-color); margin: 0 0 var(--spacing-sm); } .summary-item p { margin: 0; color: var(--gray-600); font-weight: var(--font-weight-medium); } /* Responsive Adjustments for Dashboard Components */ @media (max-width: 992px) { .content-grid { grid-template-columns: 1fr; } .quick-actions-grid { grid-template-columns: repeat(2, 1fr); } .metrics-grid { grid-template-columns: repeat(2, 1fr); } .insights-grid { grid-template-columns: 1fr; } } @media (max-width: 576px) { .quick-actions-grid { grid-template-columns: 1fr; } .metrics-grid { grid-template-columns: 1fr; } .overview-grid, .account-grid, .integrations-grid { grid-template-columns: 1fr; } .transaction-item { flex-direction: column; text-align: center; gap: var(--spacing-md); } .transaction-meta { justify-content: center; } .help-resources { grid-template-columns: 1fr; } .help-item { flex-direction: column; text-align: center; gap: var(--spacing-md); } } -------------------- END OF FILE -------------------- ### FILE 36: Relevant Reflex Shop/assets/css/main.css - Type: CSS - Size: 18.91 KB - Path: Relevant Reflex Shop/assets/css - Name: main.css ------------------------------------------------------------ /* =================================== CSS Variables =================================== */ :root { /* Colors */ --primary-color: #059669; --primary-hover: #047857; --secondary-color: #6c757d; --success-color: #28a745; --danger-color: #dc3545; --warning-color: #ffc107; --info-color: #17a2b8; --light-color: #f8f9fa; --dark-color: #212529; /* Gray Scale */ --gray-50: #f9fafb; --gray-100: #f3f4f6; --gray-200: #e5e7eb; --gray-300: #d1d5db; --gray-400: #9ca3af; --gray-500: #6b7280; --gray-600: #4b5563; --gray-700: #374151; --gray-800: #1f2937; --gray-900: #111827; /* Spacing */ --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 32px; --spacing-2xl: 48px; /* Font Sizes */ --font-size-xs: 12px; --font-size-sm: 14px; --font-size-base: 16px; --font-size-lg: 18px; --font-size-xl: 20px; --font-size-2xl: 24px; --font-size-3xl: 30px; /* Border Radius */ --border-radius-sm: 4px; --border-radius-md: 8px; --border-radius-lg: 12px; --border-radius-xl: 16px; --border-radius-full: 9999px; /* Shadows */ --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1); /* Transitions */ --transition-fast: 150ms ease-in-out; --transition-base: 250ms ease-in-out; --transition-slow: 350ms ease-in-out; } /* =================================== Reset & Base Styles =================================== */ * { margin: 0; padding: 0; box-sizing: border-box; } html { font-size: 16px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; font-size: var(--font-size-base); line-height: 1.5; color: var(--dark-color); background: var(--gray-50); min-height: 100vh; } /* =================================== Header & Navigation =================================== */ .main-header { background: white; border-bottom: 1px solid var(--gray-200); position: sticky; top: 0; z-index: 100; box-shadow: var(--shadow-sm); } .navbar { padding: 0; } .nav-container { max-width: 1400px; margin: 0 auto; padding: 0 var(--spacing-lg); display: flex; align-items: center; justify-content: space-between; min-height: 64px; } .nav-brand { display: flex; align-items: center; gap: var(--spacing-md); font-weight: 700; font-size: var(--font-size-lg); color: var(--dark-color); } .logo { width: 40px; height: 40px; background: linear-gradient(135deg, var(--primary-color), #047857); color: white; border-radius: var(--border-radius-lg); display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 18px; } .nav-menu { display: flex; align-items: center; gap: var(--spacing-sm); flex: 1; justify-content: flex-end; } .nav-link { display: flex; align-items: center; gap: var(--spacing-sm); padding: var(--spacing-sm) var(--spacing-md); color: var(--gray-700); text-decoration: none; border-radius: var(--border-radius-md); transition: all var(--transition-fast); font-size: var(--font-size-sm); font-weight: 500; white-space: nowrap; } .nav-link:hover { background: var(--gray-100); color: var(--primary-color); } .nav-link.active { background: var(--primary-color); color: white; } .nav-icon { font-size: 18px; } .mobile-toggle { display: none; flex-direction: column; gap: 4px; background: none; border: none; padding: var(--spacing-sm); cursor: pointer; } .mobile-toggle span { width: 24px; height: 2px; background: var(--dark-color); transition: var(--transition-fast); } /* =================================== Page Container & Layout =================================== */ .page-container { max-width: 1400px; margin: 0 auto; padding: var(--spacing-2xl) var(--spacing-lg); } .page-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--spacing-2xl); gap: var(--spacing-lg); } .page-header h1 { font-size: var(--font-size-3xl); font-weight: 700; color: var(--dark-color); margin-bottom: var(--spacing-xs); } .page-subtitle { font-size: var(--font-size-base); color: var(--gray-600); margin: 0; } .header-actions { display: flex; gap: var(--spacing-md); } /* =================================== Buttons =================================== */ .btn { display: inline-flex; align-items: center; gap: var(--spacing-sm); padding: 10px 20px; border: none; border-radius: var(--border-radius-md); font-size: var(--font-size-sm); font-weight: 600; cursor: pointer; transition: all var(--transition-fast); text-decoration: none; white-space: nowrap; } .btn-primary { background: var(--primary-color); color: white; } .btn-primary:hover { background: var(--primary-hover); transform: translateY(-1px); box-shadow: var(--shadow-md); } .btn-secondary { background: var(--gray-200); color: var(--gray-700); } .btn-secondary:hover { background: var(--gray-300); } /* =================================== Statistics Cards =================================== */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); margin-bottom: var(--spacing-2xl); } .stat-card { background: white; padding: var(--spacing-xl); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); display: flex; align-items: center; gap: var(--spacing-lg); transition: var(--transition-base); } .stat-card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); } .stat-icon { font-size: 48px; opacity: 0.9; } .stat-content h3 { font-size: var(--font-size-3xl); font-weight: 700; color: var(--dark-color); margin-bottom: var(--spacing-xs); } .stat-content p { font-size: var(--font-size-sm); color: var(--gray-600); margin: 0 0 var(--spacing-xs) 0; } .stat-change { font-size: var(--font-size-xs); font-weight: 600; padding: 2px 8px; border-radius: var(--border-radius-sm); } .stat-change.positive { background: #d4edda; color: #155724; } .stat-change.negative { background: #f8d7da; color: #721c24; } .stat-change.neutral { background: var(--gray-100); color: var(--gray-600); } /* =================================== User Stats (Alternative Layout) =================================== */ .user-stats { background: white; padding: var(--spacing-xl); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); margin-bottom: var(--spacing-2xl); } .stats-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--spacing-xl); } .stat-item { text-align: center; } .stat-number { font-size: var(--font-size-3xl); font-weight: 700; color: var(--primary-color); margin-bottom: var(--spacing-xs); } .stat-label { font-size: var(--font-size-sm); color: var(--gray-600); } /* =================================== Table Controls (Search & Filters) =================================== */ .table-controls { display: flex; gap: var(--spacing-md); margin-bottom: var(--spacing-lg); flex-wrap: wrap; } .search-container { flex: 1; min-width: 250px; } .search-input { width: 100%; padding: 10px 16px; border: 1px solid var(--gray-300); border-radius: var(--border-radius-md); font-size: var(--font-size-sm); transition: var(--transition-fast); } .search-input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1); } .filter-container { display: flex; gap: var(--spacing-md); flex-wrap: wrap; } .filter-select { padding: 10px 16px; border: 1px solid var(--gray-300); border-radius: var(--border-radius-md); font-size: var(--font-size-sm); background: white; cursor: pointer; transition: var(--transition-fast); } .filter-select:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1); } /* =================================== Tables - FIXED LAYOUT =================================== */ .table-container { background: white; border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); overflow-x: auto; overflow-y: visible; } .data-table { width: 100%; border-collapse: collapse; font-size: var(--font-size-sm); table-layout: fixed; min-width: 100%; } .data-table thead { background: var(--gray-50); border-bottom: 2px solid var(--gray-200); } .data-table th { padding: 16px 12px; text-align: left; font-weight: 600; color: var(--gray-700); text-transform: uppercase; font-size: var(--font-size-xs); letter-spacing: 0.5px; white-space: nowrap; position: sticky; top: 0; background: var(--gray-50); z-index: 10; } .data-table td { padding: 16px 12px; border-bottom: 1px solid var(--gray-100); vertical-align: middle; overflow: hidden; text-overflow: ellipsis; } .data-table tbody tr { transition: var(--transition-fast); } .data-table tbody tr:hover { background: var(--gray-50); } .data-table tbody tr:last-child td { border-bottom: none; } /* =================================== Badges =================================== */ .role-badge, .status-badge { display: inline-block; padding: 4px 12px; border-radius: var(--border-radius-full); font-size: var(--font-size-xs); font-weight: 600; text-transform: uppercase; white-space: nowrap; } /* Role Badges */ .role-badge.role-admin { background: #e3f2fd; color: #1976d2; } .role-badge.role-manager { background: #f3e5f5; color: #7b1fa2; } /* Status Badges */ .status-badge.status-active { background: #d4edda; color: #155724; } .status-badge.status-inactive { background: #f8d7da; color: #721c24; } .status-badge.status-suspended { background: #fff3cd; color: #856404; } .status-badge.status-open { background: #d1ecf1; color: #0c5460; } .status-badge.status-pending { background: #fff3cd; color: #856404; } .status-badge.status-resolved, .status-badge.status-completed { background: #d4edda; color: #155724; } .status-badge.status-closed { background: var(--gray-200); color: var(--gray-700); } /* =================================== Messages =================================== */ .success-message, .error-message { padding: 16px 20px; border-radius: var(--border-radius-md); margin-bottom: var(--spacing-lg); display: flex; align-items: center; gap: var(--spacing-md); font-size: var(--font-size-sm); } .success-message { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; } .error-message { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; } .message-icon { font-size: 20px; } /* =================================== Forms =================================== */ .form-group { margin-bottom: var(--spacing-lg); } .form-group label { display: block; margin-bottom: var(--spacing-sm); color: var(--gray-700); font-weight: 600; font-size: var(--font-size-sm); } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 10px 16px; border: 1px solid var(--gray-300); border-radius: var(--border-radius-md); font-size: var(--font-size-sm); transition: var(--transition-fast); font-family: inherit; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1); } .form-group input:disabled, .form-group select:disabled { background: var(--gray-100); cursor: not-allowed; } .form-help { display: block; margin-top: var(--spacing-xs); font-size: var(--font-size-xs); color: var(--gray-600); } .form-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--spacing-lg); } .form-actions { display: flex; gap: var(--spacing-md); margin-top: var(--spacing-xl); justify-content: flex-end; } /* =================================== Modal =================================== */ .modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center; padding: var(--spacing-lg); } .modal-content { background: white; border-radius: var(--border-radius-xl); padding: var(--spacing-2xl); max-width: 500px; width: 100%; max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-xl); } .modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--spacing-xl); } .modal-header h2 { font-size: var(--font-size-2xl); color: var(--dark-color); margin: 0; } .modal-close { background: none; border: none; font-size: 28px; color: var(--gray-500); cursor: pointer; padding: 0; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: var(--border-radius-md); transition: var(--transition-fast); } .modal-close:hover { background: var(--gray-100); color: var(--gray-700); } /* =================================== Dashboard Specific =================================== */ .dashboard-container { /* Dashboard specific styles */ } .quick-actions-section h2 { font-size: var(--font-size-2xl); font-weight: 700; color: var(--dark-color); margin-bottom: var(--spacing-lg); } .quick-actions-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); } .quick-action-card { background: white; padding: var(--spacing-xl); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); transition: var(--transition-base); text-align: center; } .quick-action-card:hover { box-shadow: var(--shadow-lg); transform: translateY(-4px); } .action-icon { font-size: 48px; margin-bottom: var(--spacing-md); } .quick-action-card h3 { font-size: var(--font-size-lg); color: var(--dark-color); margin-bottom: var(--spacing-sm); } .quick-action-card p { font-size: var(--font-size-sm); color: var(--gray-600); margin: 0; } /* =================================== Settings & Tabs =================================== */ .settings-container { display: grid; grid-template-columns: 250px 1fr; gap: var(--spacing-2xl); } .settings-nav { background: white; padding: var(--spacing-lg); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); height: fit-content; position: sticky; top: 80px; } .settings-tabs { list-style: none; } .settings-tabs li { margin-bottom: var(--spacing-xs); } .tab-link { display: block; padding: var(--spacing-md); color: var(--gray-700); text-decoration: none; border-radius: var(--border-radius-md); transition: var(--transition-fast); font-size: var(--font-size-sm); font-weight: 500; } .tab-link:hover { background: var(--gray-100); color: var(--primary-color); } .tab-link.active { background: var(--primary-color); color: white; } .settings-content { background: white; padding: var(--spacing-2xl); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); } .settings-section { margin-bottom: var(--spacing-2xl); } .settings-section:last-child { margin-bottom: 0; } .settings-section h2 { font-size: var(--font-size-2xl); color: var(--dark-color); margin-bottom: var(--spacing-lg); padding-bottom: var(--spacing-md); border-bottom: 2px solid var(--gray-200); } .tab-content { display: none; } .tab-content.active { display: block; } /* =================================== Responsive Design =================================== */ @media (max-width: 1024px) { .settings-container { grid-template-columns: 1fr; } .settings-nav { position: static; } } @media (max-width: 768px) { .page-container { padding: var(--spacing-lg); } .page-header { flex-direction: column; } .header-actions { width: 100%; } .stats-grid, .quick-actions-grid { grid-template-columns: 1fr; } .stats-row { grid-template-columns: repeat(2, 1fr); } .mobile-toggle { display: flex; } .nav-menu { display: none; position: absolute; top: 64px; left: 0; right: 0; background: white; flex-direction: column; padding: var(--spacing-lg); border-top: 1px solid var(--gray-200); box-shadow: var(--shadow-lg); } .nav-menu.active { display: flex; } .nav-link { width: 100%; justify-content: flex-start; } .table-container { overflow-x: scroll; } .form-row { grid-template-columns: 1fr; } } @media (max-width: 480px) { .stats-row { grid-template-columns: 1fr; } .table-controls { flex-direction: column; } .search-container, .filter-container { width: 100%; } .filter-container { flex-direction: column; } .filter-select { width: 100%; } } /* =================================== Utility Classes =================================== */ .text-center { text-align: center; } .text-right { text-align: right; } .mt-0 { margin-top: 0; } .mt-1 { margin-top: var(--spacing-xs); } .mt-2 { margin-top: var(--spacing-sm); } .mt-3 { margin-top: var(--spacing-md); } .mt-4 { margin-top: var(--spacing-lg); } .mt-5 { margin-top: var(--spacing-xl); } .mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: var(--spacing-xs); } .mb-2 { margin-bottom: var(--spacing-sm); } .mb-3 { margin-bottom: var(--spacing-md); } .mb-4 { margin-bottom: var(--spacing-lg); } .mb-5 { margin-bottom: var(--spacing-xl); } .skip-link { position: absolute; top: -40px; left: 0; background: var(--primary-color); color: white; padding: 8px; text-decoration: none; z-index: 100; } .skip-link:focus { top: 0; } -------------------- END OF FILE -------------------- ### FILE 37: Relevant Reflex Shop/assets/css/member-enhancements.css - Type: CSS - Size: 9.09 KB - Path: Relevant Reflex Shop/assets/css - Name: member-enhancements.css ------------------------------------------------------------ /* MEMBER MANAGEMENT ENHANCEMENTS CSS Add this to your main.css or include separately Improves member_view.php and member_edit.php styling */ /* Navigation Fix - Add to all member pages */ .page-cache-control { /* Prevents caching issues with back button */ } /* Enhanced Page Header */ .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 2px solid #e5e7eb; } .page-header h1 { font-size: 32px; font-weight: 700; color: #111827; margin: 0 0 8px 0; } .page-subtitle { font-size: 16px; color: #6b7280; margin: 0; } .header-actions { display: flex; gap: 12px; } /* Enhanced Buttons */ .btn { padding: 10px 20px; border-radius: 8px; border: none; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.2s; text-decoration: none; display: inline-flex; align-items: center; gap: 8px; } .btn-primary { background: linear-gradient(135deg, #059669, #047857); color: white; } .btn-primary:hover { background: linear-gradient(135deg, #047857, #047857); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(5,150,105,0.3); } .btn-secondary { background: #f3f4f6; color: #374151; } .btn-secondary:hover { background: #e5e7eb; } /* Statistics Cards - Member View */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 32px; } .stat-card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 4px 12px rgba(0,0,0,0.08); transition: transform 0.2s; } .stat-card:hover { transform: translateY(-4px); box-shadow: 0 8px 24px rgba(0,0,0,0.12); } .stat-icon { width: 48px; height: 48px; background: #059669; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; margin-bottom: 16px; } .stat-content h3 { font-size: 32px; font-weight: 700; color: #111827; margin: 0 0 8px 0; } .stat-content p { font-size: 14px; color: #6b7280; margin: 0 0 8px 0; } .stat-meta { font-size: 12px; color: #9ca3af; } /* Enhanced Tab Navigation */ .settings-container { display: grid; grid-template-columns: 240px 1fr; gap: 24px; margin-top: 24px; } .settings-nav { background: white; border-radius: 12px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); position: sticky; top: 24px; height: fit-content; } .settings-tabs { list-style: none; padding: 0; margin: 0; } .settings-tabs li { margin-bottom: 8px; } .tab-link { display: block; padding: 12px 16px; color: #6b7280; text-decoration: none; border-radius: 8px; font-weight: 500; transition: all 0.2s; } .tab-link:hover { background: #f3f4f6; color: #111827; } .tab-link.active { background: linear-gradient(135deg, #059669, #047857); color: white; } .settings-content { background: white; border-radius: 12px; padding: 32px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .tab-content { display: none; } .tab-content.active { display: block; } .settings-section { margin-bottom: 32px; } .settings-section h2 { font-size: 20px; font-weight: 700; color: #111827; margin-bottom: 20px; padding-bottom: 12px; border-bottom: 2px solid #e5e7eb; } /* Enhanced Form Styling */ .form-group { margin-bottom: 20px; } .form-group label { display: block; font-size: 14px; font-weight: 600; color: #374151; margin-bottom: 8px; } .form-group input, .form-group select, .form-group textarea { width: 100%; padding: 12px 16px; border: 2px solid #e5e7eb; border-radius: 8px; font-size: 14px; transition: all 0.2s; font-family: inherit; } .form-group input:focus, .form-group select:focus, .form-group textarea:focus { outline: none; border-color: #059669; box-shadow: 0 0 0 3px rgba(5,150,105,0.1); } .form-group input:disabled { background: #f9fafb; color: #9ca3af; cursor: not-allowed; } .form-help { display: block; font-size: 12px; color: #6b7280; margin-top: 6px; } .form-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .form-actions { margin-top: 32px; padding-top: 20px; border-top: 2px solid #e5e7eb; display: flex; gap: 12px; } /* Info Display Styling */ .info-item { margin-bottom: 20px; } .info-item label { display: block; font-size: 12px; font-weight: 600; color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px; } .info-item p { font-size: 16px; color: #111827; margin: 0; } /* Progress Bar */ .progress-container { display: flex; align-items: center; gap: 12px; } .progress-bar { flex: 1; height: 8px; background: #e5e7eb; border-radius: 4px; overflow: hidden; } .progress-fill { height: 100%; background: linear-gradient(to right, #059669, #047857); transition: width 0.3s ease; } .progress-text { font-size: 14px; font-weight: 600; color: #6b7280; min-width: 48px; } /* Transaction Item Styling */ .transactions-list { display: flex; flex-direction: column; gap: 16px; } .transaction-item { display: flex; align-items: center; gap: 16px; padding: 16px; border-radius: 12px; background: #f9fafb; border-left: 4px solid #e5e7eb; } .transaction-item.revenue { background: #ecfdf5; border-left-color: #059669; } .transaction-item.expense { background: #fef2f2; border-left-color: #dc2626; } .transaction-icon { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; font-size: 20px; border-radius: 50%; background: white; } .transaction-details { flex: 1; } .transaction-description { font-size: 14px; font-weight: 600; color: #111827; margin-bottom: 4px; } .transaction-meta { display: flex; gap: 12px; flex-wrap: wrap; } .transaction-date, .transaction-category { font-size: 12px; color: #6b7280; } .transaction-amount { font-size: 18px; font-weight: 700; } .transaction-amount.positive { color: #059669; } .transaction-amount.negative { color: #dc2626; } /* Ticket Item Styling */ .tickets-container { display: grid; gap: 16px; } .ticket-item { background: white; border: 2px solid #e5e7eb; border-radius: 12px; padding: 20px; transition: all 0.2s; } .ticket-item:hover { border-color: #059669; box-shadow: 0 4px 12px rgba(5,150,105,0.1); } .ticket-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; } .ticket-id { font-family: monospace; font-weight: 700; color: #6b7280; font-size: 14px; } .ticket-content h4 { font-size: 16px; font-weight: 600; color: #111827; margin: 0 0 12px 0; } .ticket-meta { display: flex; gap: 12px; align-items: center; } .priority-badge { padding: 4px 12px; border-radius: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; } .priority-low { background: #dbeafe; color: #1e40af; } .priority-medium { background: #fef3c7; color: #92400e; } .priority-high { background: #fee2e2; color: #991b1b; } .priority-critical { background: #047857; color: white; } .ticket-date { font-size: 12px; color: #6b7280; } /* Responsive Adjustments */ @media (max-width: 1024px) { .settings-container { grid-template-columns: 1fr; } .settings-nav { position: static; } .form-row { grid-template-columns: 1fr; } } @media (max-width: 768px) { .page-header { flex-direction: column; align-items: flex-start; gap: 16px; } .stats-grid { grid-template-columns: repeat(2, 1fr); } .transaction-item { flex-direction: column; align-items: flex-start; } } @media (max-width: 480px) { .stats-grid { grid-template-columns: 1fr; } .header-actions { width: 100%; flex-direction: column; } .btn { width: 100%; justify-content: center; } } /* Back Button Prevention - Add this JavaScript */ /* */ -------------------- END OF FILE -------------------- ### FILE 38: Relevant Reflex Shop/assets/css/responsive.css - Type: CSS - Size: 18.5 KB - Path: Relevant Reflex Shop/assets/css - Name: responsive.css ------------------------------------------------------------ /* Mobile First Responsive Design for Relevant Reflex */ /* Base Mobile Styles (up to 576px) */ @media (max-width: 576px) { :root { --font-size-base: 14px; --font-size-lg: 16px; --font-size-xl: 18px; --font-size-2xl: 20px; --font-size-3xl: 24px; --spacing-md: 0.75rem; --spacing-lg: 1rem; --spacing-xl: 1.5rem; } .nav-container { padding: 0 var(--spacing-md); height: 60px; } .brand-name { display: none; } .logo { width: 35px; height: 35px; font-size: var(--font-size-base); } .nav-menu { position: fixed; top: 60px; left: -100%; width: 100%; height: calc(100vh - 60px); background: var(--white); flex-direction: column; padding: var(--spacing-xl); gap: var(--spacing-md); transition: left var(--transition-normal); box-shadow: var(--shadow-xl); z-index: var(--z-modal); overflow-y: auto; } .nav-menu.active { left: 0; } .mobile-toggle { display: flex !important; } .nav-link { justify-content: flex-start; padding: var(--spacing-lg); border-radius: var(--border-radius-xl); font-size: var(--font-size-base); width: 100%; } .nav-link::before { display: none; } .nav-icon { font-size: var(--font-size-lg); } .page-container { padding: 0 var(--spacing-md); } .page-header { flex-direction: column; align-items: flex-start; gap: var(--spacing-md); margin-bottom: var(--spacing-xl); } .page-header h1 { font-size: var(--font-size-3xl); } .header-actions { width: 100%; justify-content: flex-start; flex-wrap: wrap; } .btn { font-size: var(--font-size-sm); padding: var(--spacing-md); min-height: 40px; } .btn-small { padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-xs); min-height: 32px; } .stats-grid { grid-template-columns: 1fr; gap: var(--spacing-md); } .stat-card { padding: var(--spacing-lg); flex-direction: row; gap: var(--spacing-md); } .stat-icon { width: 50px; height: 50px; font-size: 2rem; } .stat-content h3 { font-size: var(--font-size-xl); } .table-container { overflow-x: auto; border-radius: var(--border-radius-lg); } .data-table { min-width: 700px; } .data-table th, .data-table td { padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-xs); } .user-info, .item-info, .panel-info { gap: var(--spacing-sm); } .user-avatar { width: 32px; height: 32px; font-size: var(--font-size-xs); } .action-buttons { flex-direction: column; gap: 2px; } .btn-edit, .btn-delete, .btn-view, .btn-analytics, .btn-track, .btn-launch { min-width: 28px; height: 28px; padding: 2px; } .form-row { grid-template-columns: 1fr; gap: var(--spacing-md); } .table-controls { flex-direction: column; align-items: stretch; gap: var(--spacing-md); } .filter-container { flex-direction: column; gap: var(--spacing-sm); } .search-container { max-width: none; } .modal { padding: var(--spacing-md); } .modal-content { max-height: 95vh; } .modal-form, .settings-form, .support-form { padding: var(--spacing-lg); } } /* Mobile Landscape and Small Tablets (577px - 768px) */ @media (min-width: 577px) and (max-width: 768px) { .nav-menu { position: fixed; top: 70px; left: -100%; width: 100%; height: calc(100vh - 70px); background: var(--white); flex-direction: column; padding: var(--spacing-xl); gap: var(--spacing-md); transition: left var(--transition-normal); box-shadow: var(--shadow-xl); z-index: var(--z-modal); } .nav-menu.active { left: 0; } .mobile-toggle { display: flex; } .nav-link { justify-content: flex-start; padding: var(--spacing-lg); border-radius: var(--border-radius-xl); width: 100%; } .stats-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-lg); } .page-header { flex-direction: column; align-items: flex-start; gap: var(--spacing-md); } .form-row { grid-template-columns: 1fr; gap: var(--spacing-md); } .table-controls { flex-direction: row; flex-wrap: wrap; } .filter-container { flex-direction: row; gap: var(--spacing-md); } } /* Tablets (769px - 992px) */ @media (min-width: 769px) and (max-width: 992px) { .nav-container { padding: 0 var(--spacing-lg); } .page-container { padding: 0 var(--spacing-lg); } .stats-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-lg); } .content-grid { grid-template-columns: 1fr; gap: var(--spacing-xl); } .dashboard-content .content-grid { grid-template-columns: 2fr 1fr; } .footer-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-xl); } .nav-link { padding: var(--spacing-md); font-size: var(--font-size-sm); } .nav-icon { font-size: var(--font-size-sm); } .nav-label { font-size: var(--font-size-sm); } } /* Desktop (993px - 1199px) */ @media (min-width: 993px) and (max-width: 1199px) { .nav-container, .page-container, .footer-container { max-width: 960px; } .stats-grid { grid-template-columns: repeat(4, 1fr); } .content-grid { grid-template-columns: 2fr 1fr; } .footer-grid { grid-template-columns: repeat(4, 1fr); } } /* Large Desktop (1200px and up) */ @media (min-width: 1200px) { .nav-container, .page-container, .footer-container { max-width: 1200px; } .stats-grid { grid-template-columns: repeat(4, 1fr); } .content-grid { grid-template-columns: 2fr 1fr; gap: var(--spacing-2xl); } } /* Extra Large Screens (1400px and up) */ @media (min-width: 1400px) { .nav-container, .page-container, .footer-container { max-width: 1400px; } :root { --spacing-md: 1.25rem; --spacing-lg: 2rem; --spacing-xl: 2.5rem; --spacing-2xl: 3.5rem; } } /* Settings Page Responsive */ .settings-container { display: grid; grid-template-columns: 250px 1fr; gap: var(--spacing-2xl); margin-top: var(--spacing-xl); } .settings-nav { background: var(--white); border-radius: var(--border-radius-xl); padding: var(--spacing-lg); box-shadow: var(--shadow-sm); height: fit-content; position: sticky; top: calc(70px + var(--spacing-lg)); } .settings-tabs { list-style: none; display: flex; flex-direction: column; gap: var(--spacing-sm); } .tab-link { display: block; padding: var(--spacing-md); text-decoration: none; color: var(--gray-600); border-radius: var(--border-radius-lg); transition: all var(--transition-normal); font-weight: var(--font-weight-medium); } .tab-link:hover { background: var(--primary-light); color: var(--primary-color); } .tab-link.active { background: var(--primary-color); color: var(--white); } .settings-content { background: var(--white); border-radius: var(--border-radius-xl); box-shadow: var(--shadow-sm); } .tab-content { display: none; padding: var(--spacing-2xl); } .tab-content.active { display: block; } @media (max-width: 768px) { .settings-container { grid-template-columns: 1fr; gap: var(--spacing-lg); } .settings-nav { position: static; order: -1; } .settings-tabs { flex-direction: row; overflow-x: auto; gap: var(--spacing-xs); padding-bottom: var(--spacing-sm); } .tab-link { white-space: nowrap; padding: var(--spacing-sm) var(--spacing-md); font-size: var(--font-size-sm); } } /* Dashboard Responsive Specific */ .dashboard-container { max-width: 1200px; margin: 0 auto; padding: 0 var(--spacing-md); } @media (max-width: 768px) { .dashboard-container { padding: 0 var(--spacing-md); } .content-grid { grid-template-columns: 1fr; gap: var(--spacing-lg); } .quick-actions-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-md); } } @media (max-width: 480px) { .quick-actions-grid { grid-template-columns: 1fr; } } /* Table Responsive Behavior */ @media (max-width: 768px) { .table-container { border-radius: var(--border-radius-lg); } .data-table th:first-child, .data-table td:first-child { position: sticky; left: 0; background: var(--white); z-index: 10; } .data-table th { background: var(--gray-50); } .data-table tbody tr:hover th:first-child, .data-table tbody tr:hover td:first-child { background: var(--gray-50); } } /* Modal Responsive */ @media (max-width: 768px) { .modal { padding: var(--spacing-md); align-items: flex-start; padding-top: 10vh; } .modal-content { max-height: 85vh; width: 100%; margin: 0; } .modal-header { padding: var(--spacing-lg); } .modal-form, .settings-form, .support-form { padding: var(--spacing-lg); } .modal-large { max-width: none; } } /* Form Responsive */ @media (max-width: 576px) { .form-row { grid-template-columns: 1fr; gap: var(--spacing-md); } .form-actions { flex-direction: column; gap: var(--spacing-md); } .form-actions .btn { width: 100%; justify-content: center; } .table-controls { flex-direction: column; align-items: stretch; } .filter-container { flex-direction: column; gap: var(--spacing-sm); } .search-input, .filter-select { width: 100%; } } /* Stats Grid Responsive */ @media (max-width: 1200px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } } @media (max-width: 768px) { .stats-grid { grid-template-columns: 1fr; } } /* Footer Responsive */ @media (max-width: 992px) { .footer-grid { grid-template-columns: repeat(2, 1fr); gap: var(--spacing-xl); } } @media (max-width: 576px) { .footer-container { padding: var(--spacing-xl) var(--spacing-md) var(--spacing-md); } .footer-grid { grid-template-columns: 1fr; gap: var(--spacing-lg); } .footer-bottom { flex-direction: column; text-align: center; gap: var(--spacing-md); } .legal-links { flex-direction: column; align-items: center; gap: var(--spacing-sm); } .social-links { justify-content: center; } } /* Chart Responsive */ @media (max-width: 768px) { .chart-container canvas { max-width: 100%; height: auto; } .chart-section { padding: var(--spacing-md); } } /* Touch Device Optimizations */ @media (hover: none) and (pointer: coarse) { .btn { min-height: 44px; /* Apple's recommended minimum */ padding: var(--spacing-md) var(--spacing-lg); } .btn-edit, .btn-delete, .btn-view, .btn-analytics { min-width: 44px; min-height: 44px; padding: var(--spacing-md); } .nav-link { min-height: 44px; padding: var(--spacing-md) var(--spacing-lg); } .mobile-toggle { min-width: 44px; min-height: 44px; } /* Remove hover effects on touch devices */ .stat-card:hover { transform: none; box-shadow: var(--shadow-sm); } .btn-primary:hover { transform: none; } } /* High DPI Displays */ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { .logo { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } } /* Landscape Phone Orientation */ @media (max-height: 500px) and (orientation: landscape) { .nav-menu { height: calc(100vh - 70px); padding: var(--spacing-lg); } .modal { padding-top: 2vh; } .page-header { margin-bottom: var(--spacing-lg); } main { padding: var(--spacing-lg) 0; } } /* Print Optimizations */ @media print { .main-header, .main-footer, .btn, .mobile-toggle, .action-buttons, .search-input, .filter-select, .modal { display: none !important; } .page-container { max-width: none; padding: 0; margin: 0; } .stats-grid { grid-template-columns: repeat(4, 1fr); gap: var(--spacing-md); break-inside: avoid; } .stat-card { border: 1px solid var(--gray-300); box-shadow: none; break-inside: avoid; } .table-container { border: 1px solid var(--gray-300); box-shadow: none; } .data-table th { background: var(--gray-100) !important; color: var(--dark-color) !important; } body { font-size: 11pt; line-height: 1.4; color: black !important; background: white !important; } h1, h2, h3, h4 { color: black !important; } .page-header h1 { font-size: 18pt; margin-bottom: 12pt; } } /* High Contrast Mode Support */ @media (prefers-contrast: high) { :root { --primary-color: #047857; --secondary-color: #495057; --gray-300: #999999; --gray-600: #333333; } .nav-link { border: 1px solid transparent; } .nav-link.active { border-color: var(--white); } .btn { border: 2px solid currentColor; } .stat-card { border: 1px solid var(--gray-300); } } /* Reduced Motion Support */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } .modal-content { animation: none; } } /* Dark Mode Support */ @media (prefers-color-scheme: dark) { :root { --white: #1a1d20; --light-color: #212529; --gray-50: #2d3436; --gray-100: #495057; --gray-200: #6c757d; --gray-300: #adb5bd; --gray-400: #ced4da; --gray-500: #dee2e6; --gray-600: #e9ecef; --gray-700: #f1f3f4; --gray-800: #f8f9fa; --dark-color: #ffffff; --primary-light: rgba(5, 150, 105, 0.1); } body { background-color: #0d1117; color: var(--dark-color); } .main-header { background: #161b22; border-bottom: 1px solid var(--gray-700); } .nav-link:hover { background: rgba(5, 150, 105, 0.1); } .table-container, .stat-card, .modal-content, .settings-content { background: #161b22; border: 1px solid var(--gray-700); } .data-table th { background: var(--gray-800); } .search-input, .filter-select, input, textarea, select { background: #0d1117; border-color: var(--gray-700); color: var(--dark-color); } .search-input:focus, input:focus, textarea:focus, select:focus { border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1); } } /* Pagination Responsive */ .pagination-container { display: flex; justify-content: space-between; align-items: center; margin-top: var(--spacing-lg); padding: var(--spacing-lg); background: var(--white); border-radius: var(--border-radius-lg); box-shadow: var(--shadow-sm); } .pagination-controls { display: flex; gap: var(--spacing-sm); align-items: center; } .page-number { padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--border-radius); background: var(--gray-100); color: var(--gray-700); font-weight: var(--font-weight-medium); } .page-number.active { background: var(--primary-color); color: var(--white); } @media (max-width: 576px) { .pagination-container { flex-direction: column; gap: var(--spacing-md); text-align: center; } .pagination-info { order: 2; font-size: var(--font-size-sm); } .pagination-controls { order: 1; } } /* Loading States Responsive */ @media (max-width: 576px) { .loading::after { animation-duration: 2s; } } /* Support Page Responsive */ .support-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--spacing-2xl); margin-bottom: var(--spacing-2xl); } @media (max-width: 992px) { .support-grid { grid-template-columns: 1fr; gap: var(--spacing-xl); } } .help-resources { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--spacing-lg); } @media (max-width: 576px) { .help-resources { grid-template-columns: 1fr; } } /* Finance Page Responsive */ .finance-overview { margin-bottom: var(--spacing-2xl); } .finance-content .content-grid { display: grid; grid-template-columns: 2fr 1fr; gap: var(--spacing-2xl); } @media (max-width: 992px) { .finance-content .content-grid { grid-template-columns: 1fr; gap: var(--spacing-xl); } } -------------------- END OF FILE -------------------- ### FILE 39: Relevant Reflex Shop/assets/images/logo.svg - Type: SVG - Size: 0 B - Path: Relevant Reflex Shop/assets/images - Name: logo.svg ------------------------------------------------------------ [IMAGE FILE: SVG - Content not displayed] -------------------- END OF FILE -------------------- ### FILE 40: Relevant Reflex Shop/assets/js/dashboard.js - Type: JS - Size: 26.44 KB - Path: Relevant Reflex Shop/assets/js - Name: dashboard.js ------------------------------------------------------------ // Dashboard specific functionality for Relevant Reflex (function() { 'use strict'; // Dashboard module window.RR = window.RR || {}; RR.dashboard = { charts: {}, updateInterval: null, refreshRate: 30000, // 30 seconds init: function() { this.initializeCharts(); this.setupQuickActions(); this.setupRealTimeUpdates(); this.setupInteractions(); this.loadDashboardData(); }, initializeCharts: function() { // Performance Chart const performanceCanvas = document.getElementById('performanceChart'); if (performanceCanvas) { this.charts.performance = this.createPerformanceChart(performanceCanvas); } // Revenue Chart (for finance page) const revenueCanvas = document.getElementById('revenueChart'); if (revenueCanvas) { this.charts.revenue = this.createRevenueChart(revenueCanvas); } // Demand Chart (for demand page) const demandCanvas = document.getElementById('demandChart'); if (demandCanvas) { this.charts.demand = this.createDemandChart(demandCanvas); } // Setup chart period controls this.setupChartControls(); }, createPerformanceChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample performance data - replace with real API data const data = { labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4', 'Week 5', 'Week 6'], datasets: [{ label: 'Panel Responses', data: [320, 450, 380, 520, 430, 580], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() + '20', borderWidth: 3, tension: 0.4, fill: true }, { label: 'Active Users', data: [280, 390, 420, 480, 510, 540], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim() + '20', borderWidth: 3, tension: 0.4, fill: true }] }; return this.drawChart(ctx, canvas, data, 'line'); }, createRevenueChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample revenue data for last 12 months const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], datasets: [{ label: 'Revenue', data: [28500, 31200, 29800, 33500, 35200, 38900, 42100, 39800, 43500, 45230, 41800, 44600], backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), borderWidth: 1 }] }; return this.drawChart(ctx, canvas, data, 'bar'); }, createDemandChart: function(canvas) { const ctx = canvas.getContext('2d'); // Sample demand trend data const currentData = [6.2, 6.8, 7.1, 7.5, 8.2, 8.0, 8.5, 8.7, 9.1, 8.9, 8.7, 9.2]; const forecastData = [9.0, 9.2, 9.5, 9.7, 9.9, 10.2]; const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'Jan+', 'Feb+', 'Mar+', 'Apr+', 'May+', 'Jun+'], datasets: [{ label: 'Current Demand', data: [...currentData, ...Array(6).fill(null)], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--primary-color').trim() + '30', borderWidth: 3, tension: 0.4, fill: false }, { label: 'Forecast', data: [...Array(11).fill(null), currentData[11], ...forecastData], borderColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim(), backgroundColor: getComputedStyle(document.documentElement).getPropertyValue('--success-color').trim() + '30', borderWidth: 2, borderDash: [5, 5], tension: 0.4, fill: false }] }; return this.drawChart(ctx, canvas, data, 'line'); }, drawChart: function(ctx, canvas, data, type) { // Simple chart implementation (replace with Chart.js for production) const chart = { data: data, type: type, canvas: canvas, ctx: ctx }; this.renderChart(chart); return chart; }, renderChart: function(chart) { const { ctx, canvas, data, type } = chart; const padding = 40; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; // Clear canvas ctx.clearRect(0, 0, canvas.width, canvas.height); if (type === 'line') { this.renderLineChart(ctx, canvas, data, padding, chartWidth, chartHeight); } else if (type === 'bar') { this.renderBarChart(ctx, canvas, data, padding, chartWidth, chartHeight); } }, renderLineChart: function(ctx, canvas, data, padding, chartWidth, chartHeight) { const datasets = data.datasets; const labels = data.labels; // Find max and min values const allValues = datasets.flatMap(d => d.data.filter(v => v !== null)); const maxValue = Math.max(...allValues); const minValue = Math.min(...allValues); const range = maxValue - minValue || 1; // Draw axes ctx.strokeStyle = '#e9ecef'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, canvas.height - padding); ctx.lineTo(canvas.width - padding, canvas.height - padding); ctx.stroke(); // Draw grid lines ctx.strokeStyle = '#f1f3f4'; ctx.lineWidth = 1; for (let i = 1; i < 5; i++) { const y = padding + (i / 5) * chartHeight; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(canvas.width - padding, y); ctx.stroke(); } // Draw labels ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; labels.forEach((label, index) => { const x = padding + (index / (labels.length - 1)) * chartWidth; ctx.fillText(label, x, canvas.height - padding + 20); }); // Draw datasets datasets.forEach(dataset => { ctx.strokeStyle = dataset.borderColor; ctx.lineWidth = dataset.borderWidth || 2; if (dataset.borderDash) { ctx.setLineDash(dataset.borderDash); } else { ctx.setLineDash([]); } ctx.beginPath(); let firstPoint = true; dataset.data.forEach((value, index) => { if (value !== null) { const x = padding + (index / (labels.length - 1)) * chartWidth; const y = canvas.height - padding - ((value - minValue) / range) * chartHeight; if (firstPoint) { ctx.moveTo(x, y); firstPoint = false; } else { ctx.lineTo(x, y); } // Draw data points ctx.save(); ctx.fillStyle = dataset.borderColor; ctx.beginPath(); ctx.arc(x, y, 3, 0, 2 * Math.PI); ctx.fill(); ctx.restore(); } }); ctx.stroke(); }); }, renderBarChart: function(ctx, canvas, data, padding, chartWidth, chartHeight) { const dataset = data.datasets[0]; const labels = data.labels; const values = dataset.data; const maxValue = Math.max(...values); const barWidth = chartWidth / values.length * 0.8; // Draw axes ctx.strokeStyle = '#e9ecef'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, canvas.height - padding); ctx.lineTo(canvas.width - padding, canvas.height - padding); ctx.stroke(); // Draw bars ctx.fillStyle = dataset.backgroundColor; values.forEach((value, index) => { const barHeight = (value / maxValue) * chartHeight; const x = padding + (index / values.length) * chartWidth + (chartWidth / values.length - barWidth) / 2; const y = canvas.height - padding - barHeight; ctx.fillRect(x, y, barWidth, barHeight); // Draw value labels ctx.fillStyle = '#666'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText('$' + (value / 1000).toFixed(0) + 'k', x + barWidth / 2, y - 5); // Draw month labels ctx.fillText(labels[index], x + barWidth / 2, canvas.height - padding + 15); ctx.fillStyle = dataset.backgroundColor; }); }, setupChartControls: function() { // Chart period selectors const periodSelects = document.querySelectorAll('#chartPeriod, #trendPeriod, #revenuePeriod'); periodSelects.forEach(select => { select.addEventListener('change', function() { const chartId = this.id.replace('Period', 'Chart'); RR.dashboard.updateChartPeriod(chartId, this.value); }); }); }, updateChartPeriod: function(chartId, period) { const chart = this.charts[chartId.replace('Chart', '')]; if (!chart) return; // Update chart data based on period // This would typically fetch new data from API console.log(`Updating ${chartId} for period: ${period}`); // Show loading state const canvas = chart.canvas; const ctx = chart.ctx; ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#666'; ctx.font = '16px Arial'; ctx.textAlign = 'center'; ctx.fillText('Loading...', canvas.width / 2, canvas.height / 2); // Simulate API call setTimeout(() => { this.renderChart(chart); RR.toast.show('Chart updated successfully', 'success'); }, 800); }, setupQuickActions: function() { // Quick action buttons const quickActions = { createPanel: () => { window.location.href = 'panel.php?action=create'; }, manageUsers: () => { window.location.href = 'users.php'; }, viewReports: () => { window.location.href = 'finance.php?tab=reports'; }, systemSettings: () => { window.location.href = 'settings.php'; } }; // Expose to global scope window.quickActions = quickActions; // Setup quick action cards document.querySelectorAll('.quick-action-card').forEach(card => { card.addEventListener('click', function() { const action = this.getAttribute('data-action'); if (action && quickActions[action]) { quickActions[action](); } }); }); // Setup header action buttons document.getElementById('addUserBtn')?.addEventListener('click', function() { RR.modals.open('addUserModal'); }); document.getElementById('createPanelBtn')?.addEventListener('click', function() { RR.modals.open('createPanelModal'); }); document.getElementById('addSupplyBtn')?.addEventListener('click', function() { RR.modals.open('addSupplyModal'); }); document.getElementById('addTransactionBtn')?.addEventListener('click', function() { RR.modals.open('addTransactionModal'); }); }, setupRealTimeUpdates: function() { // Auto-refresh dashboard data this.updateInterval = setInterval(() => { this.updateRealTimeData(); }, this.refreshRate); // Pause updates when page is not visible document.addEventListener('visibilitychange', () => { if (document.hidden) { clearInterval(this.updateInterval); } else { this.updateInterval = setInterval(() => { this.updateRealTimeData(); }, this.refreshRate); } }); }, updateRealTimeData: function() { // Update stat cards with simulated real-time data const statCards = document.querySelectorAll('.stat-card'); statCards.forEach(card => { const valueElement = card.querySelector('h3'); if (!valueElement) return; const currentText = valueElement.textContent; // Update revenue values if (currentText.includes('$')) { const currentValue = parseInt(currentText.replace(/[^0-9]/g, '')); const variation = Math.floor(Math.random() * 200) - 100; // -100 to +100 const newValue = Math.max(0, currentValue + variation); valueElement.textContent = '$' + RR.utils.formatNumber(newValue); // Add pulse animation card.style.transform = 'scale(1.02)'; setTimeout(() => { card.style.transform = ''; }, 200); } // Update numeric values else if (/^\d+$/.test(currentText)) { const currentValue = parseInt(currentText); const variation = Math.floor(Math.random() * 6) - 3; // -3 to +3 const newValue = Math.max(0, currentValue + variation); valueElement.textContent = newValue.toString(); // Add subtle highlight card.classList.add('updated'); setTimeout(() => { card.classList.remove('updated'); }, 1000); } }); // Update charts Object.keys(this.charts).forEach(chartKey => { if (Math.random() > 0.7) { // 30% chance to update each chart this.updateChartData(this.charts[chartKey]); } }); }, updateChartData: function(chart) { if (!chart || !chart.data) return; // Add new data point and remove oldest chart.data.datasets.forEach(dataset => { if (dataset.data.length > 0) { // Generate new data point based on last value const lastValue = dataset.data[dataset.data.length - 1]; const variation = (Math.random() - 0.5) * (lastValue * 0.1); const newValue = Math.max(0, lastValue + variation); dataset.data.push(Math.round(newValue)); // Remove oldest point if we have too many if (dataset.data.length > 12) { dataset.data.shift(); } } }); this.renderChart(chart); }, setupInteractions: function() { // Activity item interactions document.querySelectorAll('.activity-item').forEach(item => { item.addEventListener('click', function() { this.classList.toggle('expanded'); }); }); // Stat card interactions document.querySelectorAll('.stat-card').forEach(card => { card.addEventListener('click', function() { const cardType = this.querySelector('p').textContent.toLowerCase(); RR.dashboard.navigateToDetails(cardType); }); }); // Export functionality document.querySelectorAll('[id$="ExportBtn"], [id$="exportBtn"]').forEach(btn => { btn.addEventListener('click', function() { RR.dashboard.exportData(this.id); }); }); // Generate report functionality document.getElementById('generateReportBtn')?.addEventListener('click', function() { RR.dashboard.generateReport(); }); }, navigateToDetails: function(cardType) { const navigationMap = { 'total users': 'users.php', 'active panels': 'panel.php', 'pending supplies': 'supply.php', 'monthly revenue': 'finance.php', 'total revenue': 'finance.php', 'net profit': 'finance.php' }; const url = navigationMap[cardType]; if (url) { window.location.href = url; } }, exportData: function(buttonId) { const button = document.getElementById(buttonId); if (!button) return; // Show loading state const originalText = button.textContent; button.textContent = 'Exporting...'; button.disabled = true; // Simulate export process setTimeout(() => { // Reset button button.textContent = originalText; button.disabled = false; // Show success message RR.toast.show('Data exported successfully!', 'success'); // Simulate file download const link = document.createElement('a'); link.href = 'data:text/csv;charset=utf-8,Sample Export Data\nColumn1,Column2,Column3\nValue1,Value2,Value3'; link.download = `export-${new Date().toISOString().split('T')[0]}.csv`; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, 2000); }, generateReport: function() { const button = document.getElementById('generateReportBtn'); if (!button) return; // Show loading state button.innerHTML = ' Generating...'; button.disabled = true; // Simulate report generation setTimeout(() => { // Reset button button.innerHTML = '📊 Generate Report'; button.disabled = false; // Show success and open modal with report preview RR.toast.show('Report generated successfully!', 'success'); // Create and show report preview modal RR.dashboard.showReportPreview(); }, 3000); }, showReportPreview: function() { const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'reportPreviewModal'; modal.innerHTML = ` `; document.body.appendChild(modal); RR.modals.open('reportPreviewModal'); }, loadDashboardData: function() { // Load initial dashboard data console.log('Loading dashboard data...'); // This would typically make API calls to load real data // For now, we'll simulate data loading with a timeout setTimeout(() => { console.log('Dashboard data loaded successfully'); }, 1000); }, // Keyboard shortcuts setupKeyboardShortcuts: function() { document.addEventListener('keydown', function(e) { // Only trigger shortcuts if not typing in input fields if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { return; } if (e.ctrlKey || e.metaKey) { switch(e.key) { case '1': e.preventDefault(); window.location.href = 'users.php'; break; case '2': e.preventDefault(); window.location.href = 'panel.php'; break; case '3': e.preventDefault(); window.location.href = 'supply.php'; break; case '4': e.preventDefault(); window.location.href = 'demand.php'; break; case '5': e.preventDefault(); window.location.href = 'finance.php'; break; case 'h': e.preventDefault(); window.location.href = 'index.php'; break; } } }); }, // Cleanup function destroy: function() { if (this.updateInterval) { clearInterval(this.updateInterval); } } }; // Initialize dashboard when DOM is ready document.addEventListener('DOMContentLoaded', function() { // Only initialize on dashboard-related pages const isDashboardPage = document.querySelector('.dashboard-container, .stats-grid, .chart-section'); if (isDashboardPage) { RR.dashboard.init(); RR.dashboard.setupKeyboardShortcuts(); } }); // Initialize dashboard function for other pages to call window.initDashboard = function() { if (RR.dashboard) { RR.dashboard.init(); } }; // Cleanup on page unload window.addEventListener('beforeunload', function() { if (RR.dashboard) { RR.dashboard.destroy(); } }); // Add CSS for updated state const style = document.createElement('style'); style.textContent = ` .stat-card.updated { background: linear-gradient(135deg, var(--primary-light) 0%, rgba(5, 150, 105, 0.05) 100%); border-color: var(--primary-color); transform: translateY(-1px); } .report-preview h3 { color: var(--primary-color); margin-bottom: var(--spacing-lg); } .report-preview h4 { color: var(--gray-700); margin: var(--spacing-lg) 0 var(--spacing-md); } .report-preview ul { list-style: none; padding-left: 0; } .report-preview li { padding: var(--spacing-sm) 0; border-bottom: 1px solid var(--gray-200); } .report-preview li:last-child { border-bottom: none; } `; document.head.appendChild(style); })(); -------------------- END OF FILE -------------------- ### FILE 41: Relevant Reflex Shop/assets/js/main.js - Type: JS - Size: 33.12 KB - Path: Relevant Reflex Shop/assets/js - Name: main.js ------------------------------------------------------------ // Relevant Reflex - Main JavaScript (function() { 'use strict'; // Global app object window.RR = window.RR || {}; // App configuration RR.config = { version: '1.0.0', apiUrl: '/api/', debounceDelay: 300, toastDuration: 3000, animationDuration: 300 }; // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', function() { RR.init(); }); // Main initialization RR.init = function() { console.log(`Relevant Reflex Panel Management System v${RR.config.version} initialized`); // Initialize core modules RR.navigation.init(); RR.forms.init(); RR.tables.init(); RR.modals.init(); RR.tooltips.init(); RR.lazyLoad.init(); RR.performance.init(); // Initialize page-specific modules if (typeof window.initDashboard === 'function') { window.initDashboard(); } }; // Navigation Module RR.navigation = { init: function() { this.setupMobileToggle(); this.setupSmoothScrolling(); this.setupActiveLinks(); }, setupMobileToggle: function() { const mobileToggle = document.querySelector('.mobile-toggle'); const navMenu = document.querySelector('.nav-menu'); if (!mobileToggle || !navMenu) return; // Toggle mobile menu mobileToggle.addEventListener('click', function(e) { e.preventDefault(); const isActive = mobileToggle.classList.contains('active'); if (isActive) { RR.navigation.closeMobileMenu(); } else { RR.navigation.openMobileMenu(); } }); // Close menu when clicking nav links navMenu.querySelectorAll('.nav-link').forEach(link => { link.addEventListener('click', function() { RR.navigation.closeMobileMenu(); }); }); // Close menu when clicking outside document.addEventListener('click', function(e) { const isClickInsideNav = navMenu.contains(e.target) || mobileToggle.contains(e.target); if (!isClickInsideNav && mobileToggle.classList.contains('active')) { RR.navigation.closeMobileMenu(); } }); // Close menu on escape key document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && mobileToggle.classList.contains('active')) { RR.navigation.closeMobileMenu(); } }); }, openMobileMenu: function() { const mobileToggle = document.querySelector('.mobile-toggle'); const navMenu = document.querySelector('.nav-menu'); mobileToggle.classList.add('active'); navMenu.classList.add('active'); mobileToggle.setAttribute('aria-expanded', 'true'); // Prevent body scroll document.body.style.overflow = 'hidden'; }, closeMobileMenu: function() { const mobileToggle = document.querySelector('.mobile-toggle'); const navMenu = document.querySelector('.nav-menu'); mobileToggle.classList.remove('active'); navMenu.classList.remove('active'); mobileToggle.setAttribute('aria-expanded', 'false'); // Restore body scroll document.body.style.overflow = ''; }, setupSmoothScrolling: function() { document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { const href = this.getAttribute('href'); if (href === '#') { e.preventDefault(); return; } const target = document.querySelector(href); if (target) { e.preventDefault(); const headerHeight = document.querySelector('.main-header')?.offsetHeight || 0; const targetPosition = target.offsetTop - headerHeight - 20; window.scrollTo({ top: targetPosition, behavior: 'smooth' }); } }); }); }, setupActiveLinks: function() { const currentPage = window.location.pathname; const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const linkPath = new URL(link.href).pathname; if (linkPath === currentPage) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } }; // Forms Module RR.forms = { init: function() { this.setupValidation(); this.setupFileUploads(); this.setupFormSubmissions(); this.setupRealTimeValidation(); }, setupValidation: function() { const forms = document.querySelectorAll('form[data-validate]'); forms.forEach(form => { form.addEventListener('submit', function(e) { if (!RR.forms.validateForm(this)) { e.preventDefault(); } }); }); }, validateForm: function(form) { const requiredFields = form.querySelectorAll('[required]'); let isValid = true; // Clear previous errors form.querySelectorAll('.error').forEach(el => el.classList.remove('error')); form.querySelectorAll('.error-message').forEach(el => el.remove()); requiredFields.forEach(field => { if (!this.validateField(field)) { isValid = false; } }); // Email validation const emailFields = form.querySelectorAll('input[type="email"]'); emailFields.forEach(field => { if (field.value && !this.isValidEmail(field.value)) { this.showFieldError(field, 'Please enter a valid email address'); isValid = false; } }); // Password confirmation const passwordField = form.querySelector('input[name="password"]'); const confirmField = form.querySelector('input[name="confirm_password"]'); if (passwordField && confirmField && passwordField.value !== confirmField.value) { this.showFieldError(confirmField, 'Passwords do not match'); isValid = false; } return isValid; }, validateField: function(field) { const value = field.value.trim(); if (!value) { this.showFieldError(field, 'This field is required'); return false; } // Minimum length validation const minLength = field.getAttribute('minlength'); if (minLength && value.length < parseInt(minLength)) { this.showFieldError(field, `Minimum ${minLength} characters required`); return false; } field.classList.remove('error'); return true; }, showFieldError: function(field, message) { field.classList.add('error'); // Remove existing error message const existingError = field.parentNode.querySelector('.error-message'); if (existingError) { existingError.remove(); } // Add new error message const errorEl = document.createElement('small'); errorEl.className = 'error-message'; errorEl.textContent = message; errorEl.style.color = 'var(--danger-color)'; errorEl.style.display = 'block'; errorEl.style.marginTop = 'var(--spacing-xs)'; field.parentNode.insertBefore(errorEl, field.nextSibling); }, isValidEmail: function(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }, setupFileUploads: function() { const fileInputs = document.querySelectorAll('input[type="file"]'); fileInputs.forEach(input => { input.addEventListener('change', function() { const file = this.files[0]; if (file) { // Validate file size (5MB limit) if (file.size > 5 * 1024 * 1024) { RR.toast.show('File size must be less than 5MB', 'error'); this.value = ''; return; } // Show file name const label = this.nextElementSibling; if (label) { label.textContent = file.name; } } }); }); }, setupFormSubmissions: function() { const submitButtons = document.querySelectorAll('.btn[type="submit"], .btn-primary'); submitButtons.forEach(btn => { btn.addEventListener('click', function(e) { const form = this.closest('form'); if (form && this.type === 'submit') { RR.forms.addLoadingState(this); // Remove loading state after form submission setTimeout(() => { RR.forms.removeLoadingState(this); }, 2000); } }); }); }, setupRealTimeValidation: function() { const inputs = document.querySelectorAll('input[required], textarea[required]'); inputs.forEach(input => { input.addEventListener('blur', function() { if (this.value.trim()) { RR.forms.validateField(this); } }); input.addEventListener('input', function() { if (this.classList.contains('error') && this.value.trim()) { RR.forms.validateField(this); } }); }); }, addLoadingState: function(button) { if (button.classList.contains('loading')) return; const originalText = button.textContent; button.setAttribute('data-original-text', originalText); button.textContent = 'Loading...'; button.disabled = true; button.classList.add('loading'); }, removeLoadingState: function(button) { const originalText = button.getAttribute('data-original-text'); if (originalText) { button.textContent = originalText; button.removeAttribute('data-original-text'); } button.disabled = false; button.classList.remove('loading'); } }; // Tables Module RR.tables = { init: function() { this.setupSearch(); this.setupSorting(); this.setupActions(); this.setupFilters(); }, setupSearch: function() { const searchInputs = document.querySelectorAll('.search-input'); searchInputs.forEach(input => { let timeout; input.addEventListener('input', function() { clearTimeout(timeout); timeout = setTimeout(() => { const query = this.value.toLowerCase().trim(); RR.tables.filterTable(query); }, RR.config.debounceDelay); }); }); }, filterTable: function(query) { const table = document.querySelector('.data-table'); if (!table) return; const rows = table.querySelectorAll('tbody tr'); let visibleCount = 0; rows.forEach(row => { const text = row.textContent.toLowerCase(); const shouldShow = !query || text.includes(query); row.style.display = shouldShow ? '' : 'none'; if (shouldShow) visibleCount++; }); // Update pagination info if exists const paginationInfo = document.querySelector('.pagination-info'); if (paginationInfo && query) { paginationInfo.textContent = `Showing ${visibleCount} of ${rows.length} results`; } }, setupSorting: function() { const headers = document.querySelectorAll('.data-table th[data-sort]'); headers.forEach(header => { header.style.cursor = 'pointer'; header.addEventListener('click', function() { const sortKey = this.getAttribute('data-sort'); const isAscending = !this.classList.contains('sort-asc'); // Remove sort classes from all headers headers.forEach(h => h.classList.remove('sort-asc', 'sort-desc')); // Add sort class to current header this.classList.add(isAscending ? 'sort-asc' : 'sort-desc'); RR.tables.sortTable(sortKey, isAscending); }); }); }, sortTable: function(sortKey, ascending) { const table = document.querySelector('.data-table tbody'); if (!table) return; const rows = Array.from(table.querySelectorAll('tr')); const sortIndex = Array.from(table.parentNode.querySelectorAll('th')).findIndex(th => th.getAttribute('data-sort') === sortKey ); if (sortIndex === -1) return; rows.sort((a, b) => { const aText = a.cells[sortIndex]?.textContent.trim() || ''; const bText = b.cells[sortIndex]?.textContent.trim() || ''; // Try numeric sort first const aNum = parseFloat(aText.replace(/[^0-9.-]/g, '')); const bNum = parseFloat(bText.replace(/[^0-9.-]/g, '')); if (!isNaN(aNum) && !isNaN(bNum)) { return ascending ? aNum - bNum : bNum - aNum; } // Fall back to string sort return ascending ? aText.localeCompare(bText) : bText.localeCompare(aText); }); // Re-append sorted rows rows.forEach(row => table.appendChild(row)); }, setupActions: function() { // Edit buttons document.addEventListener('click', function(e) { if (e.target.closest('.btn-edit')) { e.preventDefault(); const id = e.target.closest('.btn-edit').getAttribute('data-id'); RR.tables.handleEdit(id); } if (e.target.closest('.btn-delete')) { e.preventDefault(); const id = e.target.closest('.btn-delete').getAttribute('data-id'); RR.tables.handleDelete(id); } if (e.target.closest('.btn-view')) { e.preventDefault(); const id = e.target.closest('.btn-view').getAttribute('data-id'); RR.tables.handleView(id); } }); }, handleEdit: function(id) { RR.toast.show(`Edit functionality for ID: ${id}`, 'info'); // Implement edit modal or redirect }, handleDelete: function(id) { if (confirm('Are you sure you want to delete this item? This action cannot be undone.')) { // Show loading state const button = document.querySelector(`[data-id="${id}"].btn-delete`); if (button) { button.disabled = true; button.innerHTML = ''; } // Simulate API call setTimeout(() => { const row = button?.closest('tr'); if (row) { row.style.transition = 'all 0.3s ease'; row.style.opacity = '0'; row.style.transform = 'translateX(-20px)'; setTimeout(() => { row.remove(); RR.toast.show('Item deleted successfully', 'success'); }, 300); } }, 1000); } }, handleView: function(id) { RR.toast.show(`View details for ID: ${id}`, 'info'); // Implement view modal or redirect }, setupFilters: function() { const filterSelects = document.querySelectorAll('.filter-select'); filterSelects.forEach(select => { select.addEventListener('change', function() { RR.tables.applyFilters(); }); }); }, applyFilters: function() { const table = document.querySelector('.data-table'); if (!table) return; const rows = table.querySelectorAll('tbody tr'); const filters = {}; // Get all filter values document.querySelectorAll('.filter-select').forEach(select => { if (select.value) { filters[select.id] = select.value.toLowerCase(); } }); // Apply filters rows.forEach(row => { let shouldShow = true; Object.keys(filters).forEach(filterId => { const filterValue = filters[filterId]; let cellText = ''; // Get cell text based on filter type if (filterId.includes('role')) { const roleElement = row.querySelector('.role-badge'); cellText = roleElement ? roleElement.textContent.toLowerCase() : ''; } else if (filterId.includes('status')) { const statusElement = row.querySelector('.status-badge'); cellText = statusElement ? statusElement.textContent.toLowerCase() : ''; } if (cellText && !cellText.includes(filterValue)) { shouldShow = false; } }); row.style.display = shouldShow ? '' : 'none'; }); } }; // Modals Module RR.modals = { init: function() { this.setupModalTriggers(); this.setupModalClosing(); this.setupKeyboardNavigation(); }, setupModalTriggers: function() { // Generic modal triggers document.addEventListener('click', function(e) { const trigger = e.target.closest('[data-modal]'); if (trigger) { e.preventDefault(); const modalId = trigger.getAttribute('data-modal'); RR.modals.open(modalId); } }); }, setupModalClosing: function() { document.addEventListener('click', function(e) { // Close button if (e.target.closest('.modal-close')) { const modal = e.target.closest('.modal'); if (modal) RR.modals.close(modal.id); } // Backdrop click if (e.target.classList.contains('modal')) { RR.modals.close(e.target.id); } }); }, setupKeyboardNavigation: function() { document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { const openModal = document.querySelector('.modal[style*="flex"]'); if (openModal) { RR.modals.close(openModal.id); } } }); }, open: function(modalId) { const modal = document.getElementById(modalId); if (!modal) return; modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; // Focus first input const firstInput = modal.querySelector('input, textarea, select'); if (firstInput) { setTimeout(() => firstInput.focus(), 100); } // Add animation class if needed const modalContent = modal.querySelector('.modal-content'); if (modalContent) { modalContent.style.animation = 'modalAppear 0.3s ease-out'; } }, close: function(modalId) { const modal = document.getElementById(modalId); if (!modal) return; modal.style.display = 'none'; document.body.style.overflow = ''; // Reset form if exists const form = modal.querySelector('form'); if (form) { form.reset(); form.querySelectorAll('.error').forEach(el => el.classList.remove('error')); form.querySelectorAll('.error-message').forEach(el => el.remove()); } } }; // Toast Notifications RR.toast = { show: function(message, type = 'info') { // Remove existing toasts document.querySelectorAll('.toast').forEach(toast => toast.remove()); const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; toast.setAttribute('role', 'alert'); toast.setAttribute('aria-live', 'assertive'); document.body.appendChild(toast); // Show with animation setTimeout(() => toast.classList.add('show'), 100); // Auto hide setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, RR.config.animationDuration); }, RR.config.toastDuration); } }; // Tooltips Module RR.tooltips = { init: function() { this.createTooltips(); }, createTooltips: function() { const elements = document.querySelectorAll('[title]'); elements.forEach(el => { const title = el.getAttribute('title'); el.removeAttribute('title'); // Prevent default tooltip el.addEventListener('mouseenter', () => { RR.tooltips.show(el, title); }); el.addEventListener('mouseleave', () => { RR.tooltips.hide(); }); }); }, show: function(element, text) { const tooltip = document.createElement('div'); tooltip.className = 'tooltip'; tooltip.textContent = text; tooltip.style.cssText = ` position: absolute; background: var(--dark-color); color: var(--white); padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--border-radius); font-size: var(--font-size-sm); z-index: var(--z-tooltip); pointer-events: none; opacity: 0; transition: opacity 0.2s ease; white-space: nowrap; `; document.body.appendChild(tooltip); const rect = element.getBoundingClientRect(); const tooltipRect = tooltip.getBoundingClientRect(); tooltip.style.left = rect.left + (rect.width - tooltipRect.width) / 2 + 'px'; tooltip.style.top = rect.top - tooltipRect.height - 8 + 'px'; setTimeout(() => tooltip.style.opacity = '1', 10); }, hide: function() { const tooltips = document.querySelectorAll('.tooltip'); tooltips.forEach(tooltip => { tooltip.style.opacity = '0'; setTimeout(() => { if (tooltip.parentNode) { tooltip.parentNode.removeChild(tooltip); } }, 200); }); } }; // Lazy Loading Module RR.lazyLoad = { init: function() { if ('IntersectionObserver' in window) { this.setupImageLazyLoading(); this.setupContentLazyLoading(); } }, setupImageLazyLoading: function() { const images = document.querySelectorAll('img[data-src]'); if (images.length === 0) return; const imageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); images.forEach(img => imageObserver.observe(img)); }, setupContentLazyLoading: function() { const lazyElements = document.querySelectorAll('.lazy-load'); if (lazyElements.length === 0) return; const contentObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('loaded'); contentObserver.unobserve(entry.target); } }); }); lazyElements.forEach(el => contentObserver.observe(el)); } }; // Performance Module RR.performance = { init: function() { this.monitorPageLoad(); this.setupLinkPrefetching(); }, monitorPageLoad: function() { window.addEventListener('load', function() { if ('performance' in window) { const loadTime = Math.round(performance.now()); console.log(`Page loaded in ${loadTime}ms`); // Warn if load time is slow if (loadTime > 3000) { console.warn('Page load time is above 3 seconds. Consider optimization.'); } // Send to analytics if available if (typeof gtag !== 'undefined') { gtag('event', 'timing_complete', { name: 'load', value: loadTime }); } } }); }, setupLinkPrefetching: function() { // Prefetch important pages on hover const importantLinks = document.querySelectorAll('.nav-link, .btn-primary'); importantLinks.forEach(link => { link.addEventListener('mouseenter', function() { const url = this.href; if (url && url.startsWith(window.location.origin)) { RR.performance.prefetchPage(url); } }); }); }, prefetchPage: function(url) { // Check if already prefetched if (document.querySelector(`link[href="${url}"]`)) return; const link = document.createElement('link'); link.rel = 'prefetch'; link.href = url; document.head.appendChild(link); } }; // Utility Functions RR.utils = { // Debounce function debounce: function(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; }, // Throttle function throttle: function(func, limit) { let inThrottle; return function() { const args = arguments; const context = this; if (!inThrottle) { func.apply(context, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; }, // Format numbers with commas formatNumber: function(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); }, // Format currency formatCurrency: function(amount, currency = 'USD') { return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency }).format(amount); }, // Format date formatDate: function(date, options = {}) { const defaultOptions = { year: 'numeric', month: 'short', day: 'numeric' }; return new Date(date).toLocaleDateString('en-US', { ...defaultOptions, ...options }); }, // Get relative time getRelativeTime: function(date) { const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); const now = new Date(); const targetDate = new Date(date); const diffInMs = targetDate - now; const diffInDays = Math.floor(diffInMs / (1000 * 60 * 60 * 24)); if (Math.abs(diffInDays) < 1) { const diffInHours = Math.floor(diffInMs / (1000 * 60 * 60)); return rtf.format(diffInHours, 'hour'); } else { return rtf.format(diffInDays, 'day'); } }, // API helper api: async function(url, options = {}) { const defaultOptions = { headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } }; try { const response = await fetch(RR.config.apiUrl + url, { ...defaultOptions, ...options, headers: { ...defaultOptions.headers, ...options.headers } }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('API call failed:', error); RR.toast.show('An error occurred. Please try again.', 'error'); throw error; } }, // Local storage helpers (with fallbacks) storage: { get: function(key) { try { return JSON.parse(localStorage.getItem(key)); } catch { return null; } }, set: function(key, value) { try { localStorage.setItem(key, JSON.stringify(value)); return true; } catch { return false; } }, remove: function(key) { try { localStorage.removeItem(key); return true; } catch { return false; } } } }; // Expose utilities globally window.utils = RR.utils; window.showToast = RR.toast.show; })(); -------------------- END OF FILE -------------------- ### FILE 42: Relevant Reflex Shop/includes/footer.php - Type: PHP - Size: 3.88 KB - Path: Relevant Reflex Shop/includes - Name: footer.php ------------------------------------------------------------ -------------------- END OF FILE -------------------- ### FILE 43: Relevant Reflex Shop/includes/header.php - Type: PHP - Size: 9.56 KB - Path: Relevant Reflex Shop/includes - Name: header.php ------------------------------------------------------------ <?php echo isset($page_title) ? htmlspecialchars($page_title) . ' - ' . SITE_NAME : SITE_NAME . ' - Professional Panel Management System'; ?>
RR
Relevant Reflex Admin Panel
-------------------- END OF FILE -------------------- ### FILE 44: Relevant Reflex Shop/includes/navigation.php - Type: PHP - Size: 1.99 KB - Path: Relevant Reflex Shop/includes - Name: navigation.php ------------------------------------------------------------ -------------------- END OF FILE -------------------- ================================================================================ ## SUMMARY ================================================================================ Repository contains 44 files total. All file contents have been extracted and are shown above. This repository snapshot was generated on: 2026-02-22 23:02:33 ================================================================================ ## END OF REPOSITORY ================================================================================