# 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
Project Details
Industry:
# Description Qty Rate Amount
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): ?>
-------------------- 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';
?>
-------------------- 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';
?>
-------------------- 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';
?>
Basic Information
Signup Reward
₹
₹5 email + ₹ mobile
Survey Revenue Share
₹
Per survey completed by referred members
Performance Statistics
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'; }
?>
Conversion Rate
0
? ($affiliate['total_verified_signups'] / $affiliate['total_signups'] * 100)
: 0;
echo number_format($conversion, 1);
?>%
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';
?>
-------------------- 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';
?>
-------------------- 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
Location Information
Address
City
Country
Postal Code
Business Information
Total Projects
Total Revenue
₹
Account Created
Last Updated
Attachments ()
📎
No documents uploaded yet
-------------------- 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';
?>
✅
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.
#
₹
Are you sure you want to delete client ?
This action cannot be undone.
Cancel
Delete Client
-------------------- 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
-------------------- 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
RR
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.
-------------------- 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
RR
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:
Refresh this page in a few minutes
Clear your browser cache and cookies
Try accessing a different page
Contact our support team if the problem persists
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
✅
⚠
🇮🇳
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.
⏱ 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.
💱 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.
💾 Save Pricing Configuration
📄 Export Rate Card (PDF)
Last saved:
🔎 Quick Price Calculator
Test your pricing by entering an LOI. Uses the saved rates above and live exchange rates.
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);
?>
✅
All
'Invoiced','paid'=>'Paid','due'=>'Due','overdue'=>'Overdue','critical'=>'Critical','banned'=>'Banned'] as $sk=>$sl):?>
0):?>
📄
No invoices found. Invoices are auto-generated when projects are closed.
* All amounts are inclusive of applicable taxes
✎ Edit Remarks
Payment Reference
Remarks
💾 Save Cancel
-------------------- 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
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';
?>
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'];
?>
Total Members
+ in period
Active
0 ? round($d['members_active']/$d['members_total']*100) : 0; ?>% of total
Email Verified
+ in period
📋 Member Availability — non-closed project
Available
Verified active, not in any live project
Selected
Added to project selections
Assigned
URL assigned, not yet 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
In Circulation
Unredeemed balance
Lifetime Earned
+ in period
Lifetime Redeemed
in period
Pending Redemptions
value
Period Redemptions
paid out
Active Affiliates
of total
Total Signups
+ in period
Verified Signups
0 ? round($d['affiliates_verified_signups']/$d['affiliates_total_signups']*100) : 0; ?>% conversion
Commission Earned
Balance:
Pending Redemptions
to pay
Total Projects
+ in period
Total Invoiced
invoice · + in period
Project Client Status Updated
'green','Closed'=>'gray','On hold'=>'amber','Created'=>'blue','Targeted'=>'blue'][$p['status']] ?? 'gray';
?>
No projects yet
Ticket Type Status Updated
'red','pending'=>'amber','resolved'=>'green','closed'=>'gray'][$t['status']] ?? 'gray';
?>
No tickets yet
Email Gender Status Joined
✓
No members yet
Admin Activity Log
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 –
-------------------- 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
-------------------- 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';
?>
✅
⚠
Email Verified
✓ Yes' : '✗ No '; ?>
Onboarding Completed
✓ Yes' : '⏳ No '; ?>
-------------------- 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
📧 Email Address
✓ Verified
✗ Not Verified
🎂 Date of Birth
diff($dob)->y;
echo $dob->format('F j, Y');
?>
years old
📱 Mobile Number
✓ Verified
✅ Email Verified
✓ Yes' : '✗ No '; ?>
🎯 Onboarding Status
✓ Completed
⏳ Pending
💳 UPI ID
Not provided'; ?>
📄 PAN Number
Not provided'; ?>
✓ Approved
✗ Rejected
⏳ Pending
👤 Name on PAN
Not provided'; ?>
⚙ PAN Verification
✓ Approve
✗ Reject
⏳ Reset to Pending
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);
?>
:
No selections found for this member.
Project ID Project Name Client Selection Project Status Member Status Assigned At
No assigned survey URLs for this member.
Project ID Project Name Client Selection URL ID Project Status Created
No survey invitations sent to this member.
Project ID Project Name Client Selection URL ID Current Status Sent At
This member has not taken any surveys yet.
Project ID Project Name Client Selection Source Survey Status Quality LOI Clicked Completed
No surveys taken from mail.
Project ID Project Name Client Selection Survey Status Quality LOI Clicked Completed
No surveys taken from portal.
Project ID Project Name Client Selection Survey Status Quality LOI Clicked Completed
$items):
$lbl = $statusLabels[$st] ?? ucfirst($st);
?>
Project ID Project Name Client Selection Source Quality LOI Clicked Completed
📊
This member has no survey participation history yet.
$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
$response): ?>
' . htmlspecialchars($lbl) . '';
}
} else {
$label = isset($opts[$response]) ? $opts[$response] : ucwords(str_replace('_', ' ', $response));
echo '' . htmlspecialchars($label) . ' ';
}
?>
No responses recorded for this section.
📋
No profiler data available
💎
No transaction history available
Request ID
Points
Amount (₹)
UPI ID
Status
Requested
Processed
₹
Pending';
?>
🎁
No redemption requests found
🎫
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';
?>
✅
⚠
$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
diff($dob)->y;
?>
format('M j, Y'); ?>
years old
Are you sure you want to delete member ?
This action cannot be undone and will permanently delete:
Member profile and account data
All points and transaction history
Redemption requests
Support tickets and messages
Profiler data
Cancel
Delete Member
-------------------- 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';
?>
✅
⚠
$m['status'] === 'active')); ?>
Active Members
$m['email_verified'] == 1)); ?>
Email Verified
$m['mobile_verified'] == 1)); ?>
Mobile Verified
$m['onboarding_completed'] == 1)); ?>
Onboarded
Member ID
Email
Gender
Date of Birth
Postcode
Points
Status
Joined
Actions
diff($dob)->y;
?>
format('M j, Y'); ?>
years old
Are you sure you want to delete member ?
This action cannot be undone and will permanently delete all member data.
Cancel
Delete Member
-------------------- 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: ` `
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';
?>
✅
⚠
📋
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.
📋 Tax & Registration
Tax identification numbers and registration details.
CIN / Registration Number
💳 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.
💳 Bank Details
Banking information displayed on invoices for client payments.
📄 Invoice Preferences
Default text and formatting preferences for generated invoices.
-------------------- 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';
?>
✅
⚠
ID
Affiliate Code
Company/Name
Type
In-charge
Location
Contact
Hits
Verified
Status
Actions
No affiliates found. Click "Add New Affiliate" to create one.
#
-
Are you sure you want to delete affiliate ?
This action cannot be undone and will delete all associated data including signups and attachments.
Cancel
Delete Affiliate
-------------------- 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';
?>
✅
⚠
$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
Are you sure you want to delete user ?
This action cannot be undone.
Cancel
Delete 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 = `
Performance Summary Report
Generated: ${new Date().toLocaleString()}
Key Metrics
Total Users: 1,248 (+12% from last month)
Active Panels: 23 (+3 new this month)
Revenue Growth: 15.3% year-over-year
Customer Satisfaction: 98.5%
Print Report
Close
`;
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
------------------------------------------------------------
Relevant Reflex
Professional panel management and survey solutions for businesses worldwide.
Contact
Email: support@relevantreflex.com
Business Hours:
Monday - Friday: 9:00 AM - 6:00 PM
© Relevant Reflex. All rights reserved.