Project: Kayal Store basic ZIP File: public_html.zip Extracted on: 2025-09-26 02:37:51 Total Files: 30 ================================================================================ FILE: default.php TYPE: PHP SIZE: 15.99 KB ------------------------------------------------------------ Default page

You Are All Set to Go!

All you have to do now is upload your website files and start your journey. Check out how to do that below:

-------------------- END OF FILE -------------------- FILE: index.php TYPE: PHP SIZE: 24.19 KB ------------------------------------------------------------ 'Dashboard'] ]; // Get dashboard statistics $stats = getDashboardStats(); $recentTransactions = getRecentTransactions(10); $segmentPerformance = getSegmentPerformance(); $monthlyTrends = getMonthlyTrends(); // Calculate trends (mock data for now - can be replaced with actual calculation) $trends = [ 'revenue_growth' => 12.5, 'expense_growth' => -5.2, 'profit_growth' => 18.3, 'transactions_growth' => 8.1 ]; // Get recent activity for the current user $currentUser = getCurrentUser(); $recentActivity = []; if ($currentUser['role'] === 'admin') { // Admin can see all activities $recentActivity = fetchAll( "SELECT al.*, u.full_name FROM activity_logs al LEFT JOIN users u ON al.user_id = u.id ORDER BY al.created_at DESC LIMIT 15" ); } else { // Others see only their own activities $recentActivity = fetchAll( "SELECT al.*, u.full_name FROM activity_logs al LEFT JOIN users u ON al.user_id = u.id WHERE al.user_id = ? ORDER BY al.created_at DESC LIMIT 15", [$currentUser['id']] ); } // Include header include 'includes/header.php'; ?>

Welcome back, !

Here's what's happening with your fish business today.

Total Revenue
% vs last month
Total Expenses
% vs last month
Net Profit
Margin: %
Active Investments
Loans & Investments
Revenue vs Expenses Trend
Business Segments
Recent Transactions
No Recent Transactions

Start by adding your first sale or expense.

Type Description Amount Date Party
Segment Performance

No segment data available

0 ? ($segment['profit'] / $segment['revenue']) * 100 : 0; $performanceClass = $segment['profit'] >= 0 ? 'positive' : 'negative'; ?>
Revenue: | Expenses: | Margin: %
Recent Activity
by
-------------------- END OF FILE -------------------- FILE: login.php TYPE: PHP SIZE: 16.95 KB ------------------------------------------------------------ $token, 'remember_expires' => $expiry ], 'id = ?', [$user['id']]); // Set cookie setcookie('remember_token', $token, time() + (30 * 24 * 60 * 60), '/', '', true, true); } // Log successful login logSystemActivity('Login', 'User logged in: ' . $username, $user['id']); // Redirect to dashboard or intended page $redirectTo = $_GET['redirect'] ?? 'index.php'; redirect($redirectTo); } catch (Exception $e) { $error = $e->getMessage(); } } // Check for remember me cookie if (!isLoggedIn() && isset($_COOKIE['remember_token'])) { $token = $_COOKIE['remember_token']; $user = fetchRow( "SELECT * FROM users WHERE remember_token = ? AND remember_expires > NOW() AND status = 'active'", [$token] ); if ($user) { // Auto-login user $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; $_SESSION['full_name'] = $user['full_name']; $_SESSION['role'] = $user['role']; $_SESSION['login_time'] = time(); logSystemActivity('Auto Login', 'Remember me login for user: ' . $user['username'], $user['id']); redirect('index.php'); } else { // Invalid or expired token, remove cookie setcookie('remember_token', '', time() - 3600, '/', '', true, true); } } ?> Login - Kayal Aqua

Kayal Aqua

Demo Access

Click on any credential below to auto-fill the login form:

Admin Full Access
Manager Management Access
Version
-------------------- END OF FILE -------------------- FILE: logout.php TYPE: PHP SIZE: 1.27 KB ------------------------------------------------------------ null, 'remember_expires' => null ], 'remember_token = ?', [$token]); // Clear cookie setcookie('remember_token', '', time() - 3600, '/', '', true, true); } // Clear all session data $_SESSION = array(); // Destroy session cookie if (isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time() - 42000, '/'); } // Destroy session session_destroy(); // Redirect to login with success message redirect('login.php?logout=success'); } else { // Not logged in, redirect to login redirect('login.php'); } ?> -------------------- END OF FILE -------------------- FILE: manifest.json TYPE: JSON SIZE: 4.41 KB ------------------------------------------------------------ { "name": "Kayal Aqua - Fish Business Management", "short_name": "Kayal Aqua", "description": "Complete fish business management system for tracking sales, expenses, investments and more", "version": "1.0.0", "start_url": "/", "display": "standalone", "orientation": "portrait-primary", "theme_color": "#1e3a5f", "background_color": "#1e3a5f", "lang": "en-US", "scope": "/", "id": "/", "categories": ["business", "finance", "productivity"], "screenshots": [ { "src": "/assets/images/screenshots/dashboard-mobile.jpg", "sizes": "390x844", "type": "image/jpeg", "form_factor": "narrow", "label": "Dashboard view on mobile" }, { "src": "/assets/images/screenshots/dashboard-desktop.jpg", "sizes": "1280x720", "type": "image/jpeg", "form_factor": "wide", "label": "Dashboard view on desktop" }, { "src": "/assets/images/screenshots/sales-mobile.jpg", "sizes": "390x844", "type": "image/jpeg", "form_factor": "narrow", "label": "Sales management on mobile" } ], "icons": [ { "src": "/assets/images/icons/icon-72x72.png", "sizes": "72x72", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-96x96.png", "sizes": "96x96", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-128x128.png", "sizes": "128x128", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-144x144.png", "sizes": "144x144", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-152x152.png", "sizes": "152x152", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-384x384.png", "sizes": "384x384", "type": "image/png", "purpose": "maskable any" }, { "src": "/assets/images/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable any" } ], "shortcuts": [ { "name": "Add Sale", "short_name": "Sale", "description": "Quickly record a new sale", "url": "/pages/sales.php#add", "icons": [ { "src": "/assets/images/icons/shortcut-sale.png", "sizes": "96x96" } ] }, { "name": "Add Expense", "short_name": "Expense", "description": "Record a new business expense", "url": "/pages/expenses.php#add", "icons": [ { "src": "/assets/images/icons/shortcut-expense.png", "sizes": "96x96" } ] }, { "name": "Dashboard", "short_name": "Dashboard", "description": "View business overview and analytics", "url": "/", "icons": [ { "src": "/assets/images/icons/shortcut-dashboard.png", "sizes": "96x96" } ] }, { "name": "Reports", "short_name": "Reports", "description": "View business reports and analytics", "url": "/pages/reports.php", "icons": [ { "src": "/assets/images/icons/shortcut-reports.png", "sizes": "96x96" } ] } ], "edge_side_panel": { "preferred_width": 400 }, "launch_handler": { "client_mode": ["navigate-existing", "auto"] }, "handle_links": "preferred", "prefer_related_applications": false, "share_target": { "action": "/pages/sales.php", "method": "GET", "params": { "title": "title", "text": "text", "url": "url" } }, "protocol_handlers": [ { "protocol": "web+kayalaqua", "url": "/handle?type=%s" } ], "related_applications": [ { "platform": "webapp", "url": "https://store.kayalaquafarms.shop/manifest.json" } ], "file_handlers": [ { "action": "/pages/import.php", "accept": { "text/csv": [".csv"], "application/vnd.ms-excel": [".xls"], "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"] } } ], "display_override": [ "window-controls-overlay", "minimal-ui", "standalone", "browser" ] } -------------------- END OF FILE -------------------- FILE: setup.php TYPE: PHP SIZE: 16.08 KB ------------------------------------------------------------

Database Setup Failed

Could not create database tables. Please check:

Update config/database.php with correct credentials and refresh this page.

'); } // Check if admin user already exists try { $existingAdmin = fetchRow("SELECT id FROM users WHERE role = 'admin' LIMIT 1"); if ($existingAdmin) { die('

Setup Already Complete

An admin user already exists. Please delete this setup.php file for security.

Go to Login
'); } } catch (Exception $e) { // Tables might not exist yet, continue with setup } /** * Initialize database tables if they don't exist */ function initializeDatabaseTables() { global $pdo; try { // Read and execute the database schema $sqlFile = __DIR__ . '/sql/database.sql'; if (!file_exists($sqlFile)) { error_log("Database schema file not found: " . $sqlFile); return false; } $sql = file_get_contents($sqlFile); if ($sql === false) { error_log("Could not read database schema file"); return false; } // Split SQL into individual statements $statements = array_filter(array_map('trim', explode(';', $sql))); foreach ($statements as $statement) { if (!empty($statement) && !preg_match('/^\s*--/', $statement)) { try { $pdo->exec($statement); } catch (PDOException $e) { // Ignore "table already exists" errors if (strpos($e->getMessage(), 'already exists') === false) { error_log("Database setup error: " . $e->getMessage() . " | Statement: " . $statement); } } } } return true; } catch (Exception $e) { error_log("Database initialization failed: " . $e->getMessage()); return false; } } $error = ''; $success = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = sanitizeInput($_POST['username'] ?? ''); $email = sanitizeInput($_POST['email'] ?? ''); $fullName = sanitizeInput($_POST['full_name'] ?? ''); $phone = sanitizeInput($_POST['phone'] ?? ''); $password = $_POST['password'] ?? ''; $confirmPassword = $_POST['confirm_password'] ?? ''; try { // Validation if (empty($username) || empty($email) || empty($fullName) || empty($password)) { throw new Exception('All fields are required.'); } if (!isValidEmail($email)) { throw new Exception('Please enter a valid email address.'); } if (strlen($password) < 8) { throw new Exception('Password must be at least 8 characters long.'); } if ($password !== $confirmPassword) { throw new Exception('Passwords do not match.'); } if ($phone && !isValidPhone($phone)) { throw new Exception('Please enter a valid phone number.'); } // Check if username or email already exists $existingUser = fetchRow( "SELECT id FROM users WHERE username = ? OR email = ?", [$username, $email] ); if ($existingUser) { throw new Exception('Username or email already exists.'); } // Create admin user $userData = [ 'username' => $username, 'email' => $email, 'password' => hashPassword($password), 'full_name' => $fullName, 'phone' => $phone, 'role' => 'admin', 'status' => 'active', 'created_at' => date('Y-m-d H:i:s') ]; $userId = insertData('users', $userData); if ($userId) { $success = 'Admin user created successfully! Please delete this setup file and login with your credentials.'; // Log the setup activity logSystemActivity('System Setup', 'Admin user created: ' . $username, $userId); } else { throw new Exception('Failed to create admin user.'); } } catch (Exception $e) { $error = $e->getMessage(); } } ?> Setup - Kayal Aqua

Welcome to Kayal Aqua

Fish Business Management System

Important: Please delete the setup.php file from your server for security reasons.
First Time Setup: Create your admin account to get started with Kayal Aqua.
Password Requirements:
  • At least 8 characters long
  • Mix of letters, numbers, and special characters recommended
  • Avoid common passwords
Security Note: This setup file will create your first admin user. After setup is complete, please delete this file (setup.php) from your server.
-------------------- END OF FILE -------------------- FILE: simple_setup.php TYPE: PHP SIZE: 9.85 KB ------------------------------------------------------------ PDO::ERRMODE_EXCEPTION] ); } catch (PDOException $e) { die('

Database Connection Failed

Error: ' . htmlspecialchars($e->getMessage()) . '

Please check your database credentials and try again.

'); } // Check if admin exists try { $stmt = $pdo->query("SELECT COUNT(*) FROM users WHERE role = 'admin'"); $adminCount = $stmt->fetchColumn(); if ($adminCount > 0) { die('

Setup Already Complete

An admin user already exists. Please delete this file.

Go to Login
'); } } catch (PDOException $e) { // Table might not exist, continue } // Handle form submission if ($_POST) { $admin_username = trim($_POST['username'] ?? ''); $admin_email = trim($_POST['email'] ?? ''); $admin_name = trim($_POST['full_name'] ?? ''); $admin_password = $_POST['password'] ?? ''; $confirm_password = $_POST['confirm_password'] ?? ''; // Validation if (empty($admin_username) || empty($admin_email) || empty($admin_name) || empty($admin_password)) { $error = 'All fields are required.'; } elseif ($admin_password !== $confirm_password) { $error = 'Passwords do not match.'; } elseif (strlen($admin_password) < 8) { $error = 'Password must be at least 8 characters.'; } else { try { // Create tables first createTables($pdo); // Create admin user $hashedPassword = password_hash($admin_password, PASSWORD_DEFAULT); $stmt = $pdo->prepare("INSERT INTO users (username, email, full_name, password, role, status) VALUES (?, ?, ?, ?, 'admin', 'active')"); $stmt->execute([$admin_username, $admin_email, $admin_name, $hashedPassword]); $success = 'Admin user created successfully! You can now delete this file and login.'; } catch (PDOException $e) { $error = 'Error creating admin user: ' . $e->getMessage(); } } } function createTables($pdo) { $tables = [ "CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, full_name VARCHAR(100) NOT NULL, role ENUM('admin', 'manager', 'staff') DEFAULT 'staff', phone VARCHAR(15), status ENUM('active', 'inactive') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS business_segments ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, status ENUM('active', 'inactive') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, type ENUM('expense', 'revenue') NOT NULL, segment_id INT NULL, description TEXT, status ENUM('active', 'inactive') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS sales ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, quantity DECIMAL(10,2) DEFAULT 1, unit_price DECIMAL(10,2) NULL, category_id INT NULL, segment_id INT NULL, sale_date DATE NOT NULL, customer_name VARCHAR(100) NULL, customer_phone VARCHAR(15) NULL, description TEXT, invoice_number VARCHAR(50) NULL, created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS expenses ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, category_id INT NULL, segment_id INT NULL, expense_date DATE NOT NULL, description TEXT, vendor VARCHAR(200) NULL, payment_method VARCHAR(50) NULL, created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS investments ( id INT AUTO_INCREMENT PRIMARY KEY, type ENUM('investment', 'loan') NOT NULL, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, interest_rate DECIMAL(5,2) DEFAULT 0, start_date DATE NOT NULL, end_date DATE NULL, segment_id INT NULL, description TEXT, status ENUM('active', 'completed', 'cancelled') DEFAULT 'active', created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP )", "CREATE TABLE IF NOT EXISTS settings ( id INT AUTO_INCREMENT PRIMARY KEY, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP )" ]; foreach ($tables as $sql) { $pdo->exec($sql); } // Insert default data $pdo->exec("INSERT IGNORE INTO business_segments (id, name, description) VALUES (1, 'Fish Sales', 'Fresh fish retail and wholesale'), (2, 'Dry Fish Sales', 'Processed dry fish products'), (3, 'Ornamental Fish Sales', 'Decorative fish for aquariums'), (4, 'Fish Fry/Roast Shop', 'Cooked fish products'), (5, 'Fish Cutting Service', 'Fish processing and cutting service')"); $pdo->exec("INSERT IGNORE INTO categories (id, name, type, segment_id, description) VALUES (1, 'Fresh Fish Sales', 'revenue', 1, 'Revenue from fresh fish sales'), (2, 'Dry Fish Sales', 'revenue', 2, 'Revenue from dry fish products'), (3, 'Fish Purchase', 'expense', 1, 'Cost of purchasing fish'), (4, 'Transportation', 'expense', NULL, 'Transportation costs'), (5, 'Utilities', 'expense', NULL, 'Electricity, water, etc.')"); } ?> Simple Setup - Kayal Aqua


→ Go to Login

Important: Delete this simple_setup.php file after setup is complete for security.

-------------------- END OF FILE -------------------- FILE: sw.js TYPE: JS SIZE: 14.63 KB ------------------------------------------------------------ /** * Service Worker for Kayal Aqua PWA * Handles offline functionality and caching */ const CACHE_NAME = 'kayal-aqua-v1.0.0'; const API_CACHE_NAME = 'kayal-aqua-api-v1.0.0'; // Files to cache for offline functionality const STATIC_ASSETS = [ '/', '/index.php', '/login.php', '/assets/css/style.css', '/assets/css/mobile.css', '/assets/css/components.css', '/assets/js/main.js', '/assets/js/mobile.js', '/assets/js/charts.js', '/pages/dashboard.php', '/pages/sales.php', '/pages/expenses.php', '/pages/investments.php', '/pages/base.php', // Add manifest and icons '/manifest.json', '/assets/images/icons/icon-192x192.png', '/assets/images/icons/icon-512x512.png' ]; // API endpoints to cache const API_ENDPOINTS = [ '/api/dashboard.php' ]; // Install event - cache static assets self.addEventListener('install', (event) => { console.log('Service Worker: Installing...'); event.waitUntil( Promise.all([ // Cache static assets caches.open(CACHE_NAME).then((cache) => { console.log('Service Worker: Caching static assets'); return cache.addAll(STATIC_ASSETS.map(url => new Request(url, { credentials: 'same-origin' }))); }), // Cache API endpoints caches.open(API_CACHE_NAME).then((cache) => { console.log('Service Worker: Caching API endpoints'); return Promise.all( API_ENDPOINTS.map(url => { return fetch(new Request(url, { credentials: 'same-origin' })) .then(response => { if (response.ok) { return cache.put(url, response.clone()); } }) .catch(error => { console.log('Service Worker: Failed to cache API endpoint', url, error); }); }) ); }) ]).then(() => { console.log('Service Worker: Installation complete'); // Take control immediately self.skipWaiting(); }) ); }); // Activate event - clean up old caches self.addEventListener('activate', (event) => { console.log('Service Worker: Activating...'); event.waitUntil( Promise.all([ // Clean up old caches caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map((cacheName) => { if (cacheName !== CACHE_NAME && cacheName !== API_CACHE_NAME) { console.log('Service Worker: Deleting old cache', cacheName); return caches.delete(cacheName); } }) ); }), // Take control of all clients self.clients.claim() ]).then(() => { console.log('Service Worker: Activation complete'); }) ); }); // Fetch event - serve from cache when offline self.addEventListener('fetch', (event) => { const request = event.request; const url = new URL(request.url); // Only handle GET requests if (request.method !== 'GET') { return; } // Handle different types of requests if (url.pathname.startsWith('/api/')) { // API requests - network first, then cache event.respondWith(handleAPIRequest(request)); } else if (isStaticAsset(url.pathname)) { // Static assets - cache first, then network event.respondWith(handleStaticAsset(request)); } else if (isPageRequest(url.pathname)) { // Page requests - network first, then cache, then offline page event.respondWith(handlePageRequest(request)); } }); /** * Handle API requests with network-first strategy */ async function handleAPIRequest(request) { try { // Try network first const networkResponse = await fetch(request); if (networkResponse.ok) { // Update cache with fresh data const cache = await caches.open(API_CACHE_NAME); cache.put(request.url, networkResponse.clone()); return networkResponse; } throw new Error('Network response not ok'); } catch (error) { console.log('Service Worker: API network request failed, trying cache', error); // Try cache if network fails const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } // Return offline response for API requests return new Response( JSON.stringify({ error: 'Offline', message: 'This feature requires an internet connection' }), { status: 503, headers: { 'Content-Type': 'application/json' } } ); } } /** * Handle static assets with cache-first strategy */ async function handleStaticAsset(request) { // Try cache first const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } // Try network if not in cache try { const networkResponse = await fetch(request); if (networkResponse.ok) { // Add to cache for future use const cache = await caches.open(CACHE_NAME); cache.put(request.url, networkResponse.clone()); return networkResponse; } throw new Error('Network response not ok'); } catch (error) { console.log('Service Worker: Static asset request failed', error); // Return a fallback for failed static assets if (request.url.includes('.css')) { return new Response('/* Offline */', { headers: { 'Content-Type': 'text/css' } }); } if (request.url.includes('.js')) { return new Response('// Offline', { headers: { 'Content-Type': 'application/javascript' } }); } // For other assets, throw the error throw error; } } /** * Handle page requests with network-first strategy */ async function handlePageRequest(request) { try { // Try network first const networkResponse = await fetch(request); if (networkResponse.ok) { // Update cache with fresh content const cache = await caches.open(CACHE_NAME); cache.put(request.url, networkResponse.clone()); return networkResponse; } throw new Error('Network response not ok'); } catch (error) { console.log('Service Worker: Page network request failed, trying cache', error); // Try cache if network fails const cachedResponse = await caches.match(request); if (cachedResponse) { return cachedResponse; } // Return offline page if nothing else works return getOfflinePage(); } } /** * Generate offline page */ function getOfflinePage() { const offlineHTML = ` Offline - Kayal Aqua
🐟

You're Offline

It looks like you're not connected to the internet. Some features of Kayal Aqua may not be available.

Go to Dashboard
`; return new Response(offlineHTML, { headers: { 'Content-Type': 'text/html' } }); } /** * Check if request is for a static asset */ function isStaticAsset(pathname) { return pathname.includes('/assets/') || pathname.endsWith('.css') || pathname.endsWith('.js') || pathname.endsWith('.png') || pathname.endsWith('.jpg') || pathname.endsWith('.jpeg') || pathname.endsWith('.gif') || pathname.endsWith('.svg') || pathname.endsWith('.ico'); } /** * Check if request is for a page */ function isPageRequest(pathname) { return pathname.endsWith('.php') || pathname === '/' || pathname.startsWith('/pages/'); } // Handle background sync for offline actions self.addEventListener('sync', (event) => { console.log('Service Worker: Background sync triggered', event.tag); if (event.tag === 'background-sync') { event.waitUntil(handleBackgroundSync()); } }); /** * Handle background sync when connection is restored */ async function handleBackgroundSync() { try { // Get pending actions from IndexedDB or localStorage const pendingActions = await getPendingActions(); for (const action of pendingActions) { try { // Retry the failed action await retryAction(action); // Remove from pending actions if successful await removePendingAction(action.id); } catch (error) { console.log('Service Worker: Failed to retry action', action, error); } } console.log('Service Worker: Background sync completed'); } catch (error) { console.log('Service Worker: Background sync failed', error); } } /** * Get pending actions (placeholder - implement with IndexedDB) */ async function getPendingActions() { // TODO: Implement with IndexedDB for persistent storage return []; } /** * Retry a failed action */ async function retryAction(action) { // TODO: Implement retry logic for different action types console.log('Service Worker: Retrying action', action); } /** * Remove pending action after successful retry */ async function removePendingAction(actionId) { // TODO: Implement removal from IndexedDB console.log('Service Worker: Removed pending action', actionId); } // Handle push notifications (if implemented) self.addEventListener('push', (event) => { console.log('Service Worker: Push notification received'); const options = { body: event.data ? event.data.text() : 'New update available', icon: '/assets/images/icons/icon-192x192.png', badge: '/assets/images/icons/icon-192x192.png', tag: 'kayal-aqua-notification', requireInteraction: false, actions: [ { action: 'view', title: 'View', icon: '/assets/images/icons/icon-192x192.png' }, { action: 'close', title: 'Close' } ] }; event.waitUntil( self.registration.showNotification('Kayal Aqua', options) ); }); // Handle notification clicks self.addEventListener('notificationclick', (event) => { console.log('Service Worker: Notification clicked', event); event.notification.close(); if (event.action === 'view') { event.waitUntil( clients.openWindow('/') ); } }); console.log('Service Worker: Registered successfully'); -------------------- END OF FILE -------------------- FILE: test_connection.php TYPE: PHP SIZE: 5.84 KB ------------------------------------------------------------ '; echo '

🐟 Kayal Aqua - Database Connection Test

'; // Test 1: PHP PDO Extension echo '

1. Testing PHP PDO Extension

'; if (extension_loaded('pdo') && extension_loaded('pdo_mysql')) { echo '

✅ PDO and PDO_MySQL extensions are loaded

'; } else { echo '

❌ PDO or PDO_MySQL extension is missing

'; echo '

Contact your hosting provider to enable PDO_MySQL extension.

'; exit; } // Test 2: Database Connection echo '

2. Testing Database Connection

'; try { $pdo = new PDO( "mysql:host=$host;charset=utf8mb4", $username, $password, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); echo '

✅ Successfully connected to MySQL server

'; } catch (PDOException $e) { echo '

❌ Failed to connect to MySQL server

'; echo '

Error: ' . htmlspecialchars($e->getMessage()) . '

'; echo '

Common solutions:

'; echo ''; exit; } // Test 3: Database Exists echo '

3. Testing Database Access

'; try { $pdo->exec("USE $dbname"); echo '

✅ Successfully accessed database: ' . $dbname . '

'; } catch (PDOException $e) { echo '

⚠️ Database does not exist or no access: ' . $dbname . '

'; echo '

Error: ' . htmlspecialchars($e->getMessage()) . '

'; // Try to create the database echo '

Attempting to create database...

'; try { $pdo->exec("CREATE DATABASE IF NOT EXISTS $dbname CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); $pdo->exec("USE $dbname"); echo '

✅ Database created successfully!

'; } catch (PDOException $e2) { echo '

❌ Failed to create database

'; echo '

Error: ' . htmlspecialchars($e2->getMessage()) . '

'; echo '

Please create the database manually in your hosting control panel.

'; exit; } } // Test 4: Check Tables echo '

4. Checking Database Tables

'; try { $stmt = $pdo->query("SHOW TABLES"); $tables = $stmt->fetchAll(PDO::FETCH_COLUMN); if (empty($tables)) { echo '

⚠️ No tables found. This is normal for initial setup.

'; echo '

Tables will be created when you run setup.php

'; } else { echo '

✅ Found ' . count($tables) . ' tables:

'; echo ''; } } catch (PDOException $e) { echo '

❌ Error checking tables: ' . htmlspecialchars($e->getMessage()) . '

'; } // Test 5: Test Write Permissions echo '

5. Testing Write Permissions

'; try { $pdo->exec("CREATE TABLE IF NOT EXISTS test_table (id INT PRIMARY KEY AUTO_INCREMENT, test_data VARCHAR(50))"); $pdo->exec("INSERT INTO test_table (test_data) VALUES ('test')"); $pdo->exec("DROP TABLE test_table"); echo '

✅ Database write permissions are working

'; } catch (PDOException $e) { echo '

❌ Database write permissions failed

'; echo '

Error: ' . htmlspecialchars($e->getMessage()) . '

'; echo '

Your database user needs CREATE, INSERT, and DROP permissions.

'; } // Configuration Summary echo '

6. Configuration Summary

'; echo ''; echo ''; echo ''; echo ''; echo ''; $config = [ 'Host' => $host, 'Database Name' => $dbname, 'Username' => $username, 'Password' => str_repeat('*', strlen($password)), 'PHP Version' => phpversion(), 'MySQL Version' => $pdo->getAttribute(PDO::ATTR_SERVER_VERSION) ]; foreach ($config as $key => $value) { echo ''; echo ''; echo ''; echo ''; } echo '
SettingValue
' . htmlspecialchars($key) . '' . htmlspecialchars($value) . '
'; echo '

✅ Connection Test Complete!

'; echo '

'; echo 'Success! Your database connection is working properly. '; echo 'You can now proceed to run setup.php to create your admin account.'; echo '

'; echo '

'; echo 'Security Note: Please delete this test_connection.php file after setup is complete.'; echo '

'; echo ''; ?> -------------------- END OF FILE -------------------- FILE: api/dashboard.php TYPE: PHP SIZE: 12.61 KB ------------------------------------------------------------ 'Unauthorized'], 401); } $action = $_GET['action'] ?? ''; $currentUser = getCurrentUser(); try { switch ($action) { case 'revenue_expenses': $data = getRevenueExpensesData(); sendJsonResponse(['success' => true, 'data' => $data]); break; case 'business_segments': $data = getBusinessSegmentsData(); sendJsonResponse(['success' => true, 'data' => $data]); break; case 'monthly_trends': $year = $_GET['year'] ?? date('Y'); $data = getMonthlyTrendsData($year); sendJsonResponse(['success' => true, 'data' => $data]); break; case 'cash_flow': $data = getCashFlowData(); sendJsonResponse(['success' => true, 'data' => $data]); break; case 'top_products': $data = getTopProductsData(); sendJsonResponse(['success' => true, 'data' => $data]); break; case 'dashboard_stats': $data = getDashboardStatsData(); sendJsonResponse(['success' => true, 'data' => $data]); break; default: sendJsonResponse(['error' => 'Invalid action'], 400); } } catch (Exception $e) { error_log('Dashboard API Error: ' . $e->getMessage()); sendJsonResponse(['error' => 'Internal server error'], 500); } /** * Get revenue vs expenses data for chart */ function getRevenueExpensesData() { $period = $_GET['period'] ?? 'year'; $currentUser = getCurrentUser(); // Determine date range based on period switch ($period) { case 'month': $startDate = date('Y-m-01'); $endDate = date('Y-m-t'); $groupBy = 'DAY'; $dateFormat = '%Y-%m-%d'; break; case 'quarter': $startDate = date('Y-m-01', strtotime('first day of -2 months')); $endDate = date('Y-m-t'); $groupBy = 'WEEK'; $dateFormat = '%Y-%u'; break; default: // year $startDate = date('Y-01-01'); $endDate = date('Y-12-31'); $groupBy = 'MONTH'; $dateFormat = '%Y-%m'; } // Base conditions for user filtering $userCondition = ''; $params = [$startDate, $endDate]; if ($currentUser['role'] !== 'admin') { $userCondition = ' AND created_by = ?'; $params[] = $currentUser['id']; } // Get revenue data $revenueQuery = " SELECT DATE_FORMAT(sale_date, '{$dateFormat}') as period, SUM(amount) as amount FROM sales WHERE sale_date BETWEEN ? AND ? {$userCondition} GROUP BY DATE_FORMAT(sale_date, '{$dateFormat}') ORDER BY period "; $revenueData = fetchAll($revenueQuery, $params); // Get expenses data $expensesQuery = " SELECT DATE_FORMAT(expense_date, '{$dateFormat}') as period, SUM(amount) as amount FROM expenses WHERE expense_date BETWEEN ? AND ? {$userCondition} GROUP BY DATE_FORMAT(expense_date, '{$dateFormat}') ORDER BY period "; $expensesData = fetchAll($expensesQuery, $params); // Combine and format data for Chart.js $periods = array_unique(array_merge( array_column($revenueData, 'period'), array_column($expensesData, 'period') )); sort($periods); $revenue = []; $expenses = []; $labels = []; foreach ($periods as $period) { $revenueAmount = 0; $expenseAmount = 0; foreach ($revenueData as $row) { if ($row['period'] === $period) { $revenueAmount = (float)$row['amount']; break; } } foreach ($expensesData as $row) { if ($row['period'] === $period) { $expenseAmount = (float)$row['amount']; break; } } $revenue[] = $revenueAmount; $expenses[] = $expenseAmount; // Format label based on period if ($period === 'month') { $labels[] = date('M j', strtotime($period)); } elseif ($period === 'quarter') { $labels[] = 'Week ' . date('W', strtotime($period . '-01')); } else { $labels[] = date('M Y', strtotime($period . '-01')); } } return [ 'labels' => $labels, 'datasets' => [ [ 'label' => 'Revenue', 'data' => $revenue, 'backgroundColor' => 'rgba(255, 193, 7, 0.2)', 'borderColor' => 'rgba(255, 193, 7, 1)', 'borderWidth' => 2, 'fill' => true ], [ 'label' => 'Expenses', 'data' => $expenses, 'backgroundColor' => 'rgba(220, 53, 69, 0.2)', 'borderColor' => 'rgba(220, 53, 69, 1)', 'borderWidth' => 2, 'fill' => true ] ] ]; } /** * Get business segments data for pie chart */ function getBusinessSegmentsData() { $currentUser = getCurrentUser(); $userCondition = ''; $params = []; if ($currentUser['role'] !== 'admin') { $userCondition = ' AND s.created_by = ?'; $params[] = $currentUser['id']; } $query = " SELECT bs.name as segment_name, COALESCE(SUM(s.amount), 0) as revenue FROM business_segments bs LEFT JOIN sales s ON bs.id = s.segment_id {$userCondition} WHERE bs.status = 'active' GROUP BY bs.id, bs.name HAVING revenue > 0 ORDER BY revenue DESC "; $data = fetchAll($query, $params); $labels = array_column($data, 'segment_name'); $amounts = array_map(function($row) { return (float)$row['revenue']; }, $data); $colors = [ 'rgba(30, 58, 95, 1)', // Primary Navy 'rgba(255, 193, 7, 1)', // Yellow 'rgba(40, 167, 69, 1)', // Success 'rgba(23, 162, 184, 1)', // Info 'rgba(220, 53, 69, 1)' // Danger ]; return [ 'labels' => $labels, 'datasets' => [ [ 'data' => $amounts, 'backgroundColor' => array_slice($colors, 0, count($labels)), 'borderColor' => '#fff', 'borderWidth' => 2 ] ] ]; } /** * Get monthly trends data */ function getMonthlyTrendsData($year) { $currentUser = getCurrentUser(); $userCondition = ''; $params = [$year]; if ($currentUser['role'] !== 'admin') { $userCondition = ' AND created_by = ?'; $params[] = $currentUser['id']; } $query = " SELECT MONTH(transaction_date) as month, MONTHNAME(transaction_date) as month_name, SUM(CASE WHEN type = 'revenue' THEN amount ELSE 0 END) as revenue, SUM(CASE WHEN type = 'expense' THEN amount ELSE 0 END) as expenses FROM ( SELECT 'revenue' as type, amount, sale_date as transaction_date, created_by FROM sales UNION ALL SELECT 'expense' as type, amount, expense_date as transaction_date, created_by FROM expenses ) t WHERE YEAR(transaction_date) = ? {$userCondition} GROUP BY MONTH(transaction_date), MONTHNAME(transaction_date) ORDER BY MONTH(transaction_date) "; $data = fetchAll($query, $params); // Fill in missing months with zeros $months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; $revenue = array_fill(0, 12, 0); $expenses = array_fill(0, 12, 0); foreach ($data as $row) { $monthIndex = (int)$row['month'] - 1; $revenue[$monthIndex] = (float)$row['revenue']; $expenses[$monthIndex] = (float)$row['expenses']; } return [ 'labels' => $months, 'datasets' => [ [ 'label' => 'Revenue', 'data' => $revenue, 'borderColor' => 'rgba(30, 58, 95, 1)', 'backgroundColor' => 'rgba(30, 58, 95, 0.1)', 'tension' => 0.4, 'fill' => true ], [ 'label' => 'Profit', 'data' => array_map(function($r, $e) { return $r - $e; }, $revenue, $expenses), 'borderColor' => 'rgba(40, 167, 69, 1)', 'backgroundColor' => 'rgba(40, 167, 69, 0.1)', 'tension' => 0.4, 'fill' => true ] ] ]; } /** * Get cash flow data for bar chart */ function getCashFlowData() { $currentUser = getCurrentUser(); $userCondition = ''; $params = []; if ($currentUser['role'] !== 'admin') { $userCondition = ' AND created_by = ?'; $params[] = $currentUser['id']; } // Get last 4 weeks of data $query = " SELECT WEEK(transaction_date) as week_num, SUM(CASE WHEN type = 'revenue' THEN amount ELSE -amount END) as net_flow FROM ( SELECT 'revenue' as type, amount, sale_date as transaction_date, created_by FROM sales WHERE sale_date >= DATE_SUB(CURDATE(), INTERVAL 4 WEEK) UNION ALL SELECT 'expense' as type, amount, expense_date as transaction_date, created_by FROM expenses WHERE expense_date >= DATE_SUB(CURDATE(), INTERVAL 4 WEEK) ) t WHERE 1=1 {$userCondition} GROUP BY WEEK(transaction_date) ORDER BY WEEK(transaction_date) DESC LIMIT 4 "; $data = fetchAll($query, $params); $data = array_reverse($data); // Show oldest to newest $labels = []; $cashFlow = []; $colors = []; for ($i = 0; $i < count($data); $i++) { $labels[] = 'Week ' . ($i + 1); $amount = (float)$data[$i]['net_flow']; $cashFlow[] = $amount; $colors[] = $amount >= 0 ? 'rgba(40, 167, 69, 1)' : 'rgba(220, 53, 69, 1)'; } return [ 'labels' => $labels, 'datasets' => [ [ 'label' => 'Net Cash Flow', 'data' => $cashFlow, 'backgroundColor' => $colors, 'borderColor' => $colors, 'borderWidth' => 1 ] ] ]; } /** * Get top products/services data */ function getTopProductsData() { $currentUser = getCurrentUser(); $userCondition = ''; $params = []; if ($currentUser['role'] !== 'admin') { $userCondition = ' AND created_by = ?'; $params[] = $currentUser['id']; } $query = " SELECT title, SUM(amount) as total_revenue, COUNT(*) as sales_count FROM sales WHERE sale_date >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) {$userCondition} GROUP BY title ORDER BY total_revenue DESC LIMIT 5 "; $data = fetchAll($query, $params); $labels = array_column($data, 'title'); $revenues = array_map(function($row) { return (float)$row['total_revenue']; }, $data); $colors = [ 'rgba(30, 58, 95, 1)', 'rgba(255, 193, 7, 1)', 'rgba(40, 167, 69, 1)', 'rgba(23, 162, 184, 1)', 'rgba(220, 53, 69, 1)' ]; return [ 'labels' => $labels, 'datasets' => [ [ 'label' => 'Revenue', 'data' => $revenues, 'backgroundColor' => array_slice($colors, 0, count($labels)), 'borderColor' => '#fff', 'borderWidth' => 1 ] ] ]; } /** * Get dashboard statistics summary */ function getDashboardStatsData() { $currentUser = getCurrentUser(); $stats = getDashboardStats($currentUser['role'] !== 'admin' ? $currentUser['id'] : null); // Calculate growth rates (mock data for now - implement actual calculation) $growthRates = [ 'revenue_growth' => 12.5, 'expense_growth' => -5.2, 'profit_growth' => 18.3, 'transaction_growth' => 8.1 ]; return array_merge($stats, $growthRates); } ?> -------------------- END OF FILE -------------------- FILE: assets/css/components.css TYPE: CSS SIZE: 11.29 KB ------------------------------------------------------------ /* Reusable Components for Kayal Aqua */ /* Modal Component */ .modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; z-index: 1050; opacity: 0; visibility: hidden; transition: all 0.3s ease; } .modal.active { opacity: 1; visibility: visible; } .modal-content { background: var(--white); border-radius: var(--border-radius-lg); width: 90%; max-width: 500px; max-height: 90vh; overflow-y: auto; box-shadow: var(--shadow-lg); transform: scale(0.9); transition: transform 0.3s ease; } .modal.active .modal-content { transform: scale(1); } .modal-header { padding: var(--space-lg); border-bottom: 1px solid var(--light-gray); display: flex; justify-content: space-between; align-items: center; background: var(--primary-navy); color: var(--white); border-top-left-radius: var(--border-radius-lg); border-top-right-radius: var(--border-radius-lg); } .modal-title { margin: 0; font-size: var(--font-size-lg); font-weight: 600; } .modal-close { background: none; border: none; color: var(--white); font-size: 1.5rem; cursor: pointer; padding: var(--space-sm); border-radius: var(--border-radius); } .modal-close:hover { background: rgba(255, 255, 255, 0.1); } .modal-body { padding: var(--space-lg); } .modal-footer { padding: var(--space-lg); border-top: 1px solid var(--light-gray); display: flex; justify-content: flex-end; gap: var(--space-md); } /* Tabs Component */ .tabs { margin-bottom: var(--space-lg); } .tab-nav { display: flex; border-bottom: 2px solid var(--light-gray); margin-bottom: var(--space-lg); overflow-x: auto; } .tab-nav-item { background: none; border: none; padding: var(--space-md) var(--space-lg); cursor: pointer; border-bottom: 2px solid transparent; color: var(--gray); font-weight: 500; white-space: nowrap; transition: all 0.3s ease; } .tab-nav-item.active { color: var(--primary-navy); border-bottom-color: var(--primary-yellow); } .tab-nav-item:hover { color: var(--primary-navy); background: var(--light-gray); } .tab-content { display: none; } .tab-content.active { display: block; animation: fadeIn 0.3s ease; } /* Accordion Component */ .accordion-item { border: 1px solid var(--light-gray); border-radius: var(--border-radius); margin-bottom: var(--space-sm); overflow: hidden; } .accordion-header { background: var(--white); border: none; width: 100%; padding: var(--space-lg); text-align: left; cursor: pointer; display: flex; justify-content: space-between; align-items: center; font-weight: 500; color: var(--primary-navy); transition: background 0.3s ease; } .accordion-header:hover { background: var(--light-gray); } .accordion-header.active { background: var(--primary-navy); color: var(--white); } .accordion-icon { transition: transform 0.3s ease; } .accordion-header.active .accordion-icon { transform: rotate(180deg); } .accordion-content { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; } .accordion-content.active { max-height: 500px; } .accordion-body { padding: var(--space-lg); background: var(--white); border-top: 1px solid var(--light-gray); } /* Progress Bar Component */ .progress { background: var(--light-gray); border-radius: var(--border-radius); height: 8px; overflow: hidden; margin-bottom: var(--space-sm); } .progress-bar { height: 100%; background: linear-gradient(90deg, var(--primary-yellow), var(--primary-yellow-dark)); transition: width 0.6s ease; border-radius: var(--border-radius); } .progress-text { font-size: var(--font-size-sm); color: var(--gray); text-align: center; } /* Badge Component */ .badge { display: inline-flex; align-items: center; padding: 0.25rem 0.5rem; font-size: var(--font-size-xs); font-weight: 500; border-radius: 0.375rem; text-transform: uppercase; letter-spacing: 0.5px; } .badge-primary { background: var(--primary-navy); color: var(--white); } .badge-secondary { background: var(--primary-yellow); color: var(--primary-navy); } .badge-success { background: var(--success); color: var(--white); } .badge-danger { background: var(--danger); color: var(--white); } .badge-warning { background: var(--warning); color: var(--primary-navy); } .badge-info { background: var(--info); color: var(--white); } .badge-outline { background: transparent; border: 1px solid currentColor; } /* Dropdown Component */ .dropdown { position: relative; display: inline-block; } .dropdown-toggle { background: var(--white); border: 1px solid #ced4da; padding: var(--space-md); border-radius: var(--border-radius); cursor: pointer; display: flex; align-items: center; gap: var(--space-sm); min-width: 150px; } .dropdown-toggle:hover { border-color: var(--primary-yellow); } .dropdown-menu { position: absolute; top: 100%; left: 0; background: var(--white); border: 1px solid #ced4da; border-radius: var(--border-radius); box-shadow: var(--shadow-md); min-width: 200px; z-index: 1000; opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.3s ease; } .dropdown.active .dropdown-menu { opacity: 1; visibility: visible; transform: translateY(0); } .dropdown-item { display: block; width: 100%; padding: var(--space-md); background: none; border: none; text-align: left; cursor: pointer; color: var(--dark-gray); text-decoration: none; transition: background 0.3s ease; } .dropdown-item:hover { background: var(--light-gray); } .dropdown-divider { height: 1px; background: var(--light-gray); margin: var(--space-xs) 0; } /* Tooltip Component */ .tooltip { position: relative; display: inline-block; } .tooltip-text { visibility: hidden; width: 120px; background: var(--primary-navy); color: var(--white); text-align: center; border-radius: var(--border-radius); padding: var(--space-sm); font-size: var(--font-size-xs); position: absolute; z-index: 1000; bottom: 125%; left: 50%; margin-left: -60px; opacity: 0; transition: opacity 0.3s; } .tooltip-text::after { content: ""; position: absolute; top: 100%; left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; border-color: var(--primary-navy) transparent transparent transparent; } .tooltip:hover .tooltip-text { visibility: visible; opacity: 1; } /* Breadcrumb Component */ .breadcrumb { display: flex; flex-wrap: wrap; padding: var(--space-md) 0; margin-bottom: var(--space-lg); list-style: none; } .breadcrumb-item { display: flex; align-items: center; } .breadcrumb-item + .breadcrumb-item { padding-left: var(--space-sm); } .breadcrumb-item + .breadcrumb-item::before { content: "/"; color: var(--gray); padding-right: var(--space-sm); } .breadcrumb-link { color: var(--primary-navy); text-decoration: none; } .breadcrumb-link:hover { text-decoration: underline; } .breadcrumb-item.active { color: var(--gray); } /* Card with Image Component */ .image-card { background: var(--white); border-radius: var(--border-radius); overflow: hidden; box-shadow: var(--shadow-sm); transition: transform 0.3s ease, box-shadow 0.3s ease; } .image-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .image-card-img { width: 100%; height: 200px; object-fit: cover; background: var(--light-gray); } .image-card-body { padding: var(--space-lg); } .image-card-title { font-size: var(--font-size-lg); font-weight: 600; color: var(--primary-navy); margin: 0 0 var(--space-sm) 0; } .image-card-text { color: var(--gray); line-height: 1.5; } /* Timeline Component */ .timeline { position: relative; padding-left: var(--space-xl); } .timeline::before { content: ''; position: absolute; left: 15px; top: 0; height: 100%; width: 2px; background: var(--primary-yellow); } .timeline-item { position: relative; margin-bottom: var(--space-xl); } .timeline-marker { position: absolute; left: -25px; width: 12px; height: 12px; border-radius: 50%; background: var(--primary-yellow); border: 3px solid var(--white); box-shadow: 0 0 0 3px var(--primary-yellow); } .timeline-content { background: var(--white); border-radius: var(--border-radius); padding: var(--space-lg); box-shadow: var(--shadow-sm); } .timeline-date { color: var(--gray); font-size: var(--font-size-sm); margin-bottom: var(--space-sm); } .timeline-title { color: var(--primary-navy); font-weight: 600; margin-bottom: var(--space-sm); } /* Data Table Component */ .data-table-wrapper { background: var(--white); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); overflow: hidden; } .data-table-header { padding: var(--space-lg); background: var(--light-gray); border-bottom: 1px solid #dee2e6; display: flex; justify-content: space-between; align-items: center; } .data-table-title { font-size: var(--font-size-lg); font-weight: 600; color: var(--primary-navy); margin: 0; } .data-table-actions { display: flex; gap: var(--space-sm); } .data-table { width: 100%; border-collapse: collapse; } .data-table th { background: var(--primary-navy); color: var(--white); padding: var(--space-md); text-align: left; font-weight: 600; } .data-table td { padding: var(--space-md); border-bottom: 1px solid #dee2e6; } .data-table tbody tr:hover { background: var(--light-gray); } /* Empty State Component */ .empty-state { text-align: center; padding: var(--space-xxl); color: var(--gray); } .empty-state-icon { font-size: 3rem; margin-bottom: var(--space-lg); color: var(--primary-yellow); } .empty-state-title { font-size: var(--font-size-lg); color: var(--primary-navy); margin-bottom: var(--space-md); } .empty-state-text { margin-bottom: var(--space-lg); line-height: 1.5; } /* Filter Component */ .filter-bar { background: var(--white); padding: var(--space-lg); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); margin-bottom: var(--space-lg); } .filter-row { display: flex; flex-wrap: wrap; gap: var(--space-md); align-items: end; } .filter-group { flex: 1; min-width: 200px; } .filter-actions { display: flex; gap: var(--space-sm); } /* Status Indicator */ .status-indicator { display: inline-flex; align-items: center; gap: var(--space-xs); font-size: var(--font-size-sm); } .status-dot { width: 8px; height: 8px; border-radius: 50%; } .status-active .status-dot { background: var(--success); } .status-inactive .status-dot { background: var(--danger); } .status-pending .status-dot { background: var(--warning); } /* Loading States */ .skeleton { background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); background-size: 200% 100%; animation: loading 1.5s infinite; border-radius: var(--border-radius); } .skeleton-text { height: 1rem; margin-bottom: var(--space-sm); } .skeleton-title { height: 1.5rem; width: 60%; margin-bottom: var(--space-md); } .skeleton-paragraph { height: 1rem; margin-bottom: var(--space-xs); } .skeleton-paragraph:last-child { width: 40%; } @keyframes loading { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } } -------------------- END OF FILE -------------------- FILE: assets/css/mobile.css TYPE: CSS SIZE: 8.05 KB ------------------------------------------------------------ /* Mobile-specific enhancements for Kayal Aqua */ /* Touch-friendly interactions */ @media (max-width: 767px) { /* Ensure all clickable elements meet minimum touch target size */ .btn, .nav-link, .form-control, input[type="checkbox"], input[type="radio"] { min-height: 44px; min-width: 44px; } /* Improve form usability on mobile */ .form-control { font-size: 16px; /* Prevents zoom on iOS */ padding: 0.75rem; } /* Stack form elements on mobile */ .form-row { flex-direction: column; } .form-row .col { width: 100%; margin-bottom: var(--space-md); } /* Mobile-specific table styling */ .table-mobile { border: 0; } .table-mobile thead { border: none; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .table-mobile tr { border-bottom: 3px solid var(--light-gray); display: block; margin-bottom: var(--space-md); background: var(--white); border-radius: var(--border-radius); padding: var(--space-md); box-shadow: var(--shadow-sm); } .table-mobile td { border: none; display: block; font-size: var(--font-size-sm); text-align: right; padding: var(--space-sm) 0; border-bottom: 1px solid #eee; } .table-mobile td:last-child { border-bottom: none; } .table-mobile td:before { content: attr(data-label) ": "; float: left; font-weight: bold; color: var(--primary-navy); } /* Mobile navigation improvements */ .mobile-nav-item { border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .mobile-nav-item:last-child { border-bottom: none; } /* Quick action buttons for mobile */ .mobile-quick-actions { position: fixed; bottom: 20px; right: 20px; z-index: 999; } .quick-action-btn { width: 56px; height: 56px; border-radius: 50%; background: var(--primary-yellow); color: var(--primary-navy); border: none; box-shadow: var(--shadow-lg); margin-bottom: var(--space-sm); display: flex; align-items: center; justify-content: center; font-size: 1.5rem; cursor: pointer; transition: all 0.3s ease; } .quick-action-btn:hover { transform: scale(1.1); box-shadow: var(--shadow-lg); } /* Mobile-specific stats layout */ .stats-mobile { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-sm); } .stat-card-mobile { padding: var(--space-md); text-align: center; } .stat-card-mobile .stat-value { font-size: var(--font-size-lg); } .stat-card-mobile .stat-label { font-size: 0.7rem; } /* Mobile form enhancements */ .mobile-form-group { position: relative; } .mobile-form-group .form-label { position: absolute; top: 50%; left: var(--space-md); transform: translateY(-50%); color: var(--gray); pointer-events: none; transition: all 0.3s ease; } .mobile-form-group .form-control:focus + .form-label, .mobile-form-group .form-control:not(:placeholder-shown) + .form-label { top: -8px; left: var(--space-sm); font-size: var(--font-size-xs); color: var(--primary-navy); background: var(--white); padding: 0 var(--space-xs); } /* Mobile card layouts */ .mobile-card-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-md); } .mobile-card { background: var(--white); border-radius: var(--border-radius); padding: var(--space-md); box-shadow: var(--shadow-sm); } .mobile-card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: var(--space-md); padding-bottom: var(--space-sm); border-bottom: 1px solid var(--light-gray); } .mobile-card-title { font-size: var(--font-size-base); font-weight: 600; color: var(--primary-navy); margin: 0; } .mobile-card-action { background: none; border: none; color: var(--primary-yellow); font-size: 1.2rem; cursor: pointer; padding: var(--space-xs); } /* Mobile-specific utility classes */ .mobile-only { display: block !important; } .desktop-only { display: none !important; } .mobile-center { text-align: center; } .mobile-hidden { display: none; } /* Swipe gestures for mobile tables */ .swipe-container { overflow-x: auto; -webkit-overflow-scrolling: touch; } .swipe-item { min-width: 280px; display: inline-block; vertical-align: top; } /* Mobile dropdown menus */ .mobile-dropdown { position: relative; } .mobile-dropdown-content { display: none; position: absolute; background: var(--white); min-width: 200px; box-shadow: var(--shadow-md); border-radius: var(--border-radius); z-index: 1000; top: 100%; right: 0; } .mobile-dropdown.active .mobile-dropdown-content { display: block; } .mobile-dropdown-item { display: block; padding: var(--space-md); color: var(--dark-gray); text-decoration: none; border-bottom: 1px solid var(--light-gray); } .mobile-dropdown-item:last-child { border-bottom: none; } .mobile-dropdown-item:hover { background: var(--light-gray); } /* Mobile pagination */ .mobile-pagination { display: flex; justify-content: center; margin-top: var(--space-lg); } .mobile-pagination .btn { margin: 0 var(--space-xs); padding: var(--space-sm); min-width: 40px; } /* Mobile search bar */ .mobile-search { position: relative; margin-bottom: var(--space-lg); } .mobile-search-input { width: 100%; padding: var(--space-md); padding-right: 50px; border: 2px solid var(--primary-yellow); border-radius: 25px; font-size: var(--font-size-base); } .mobile-search-btn { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: var(--primary-yellow); border: none; border-radius: 50%; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; color: var(--primary-navy); } /* Pull-to-refresh indicator */ .pull-refresh { text-align: center; padding: var(--space-lg); color: var(--gray); font-size: var(--font-size-sm); } /* Mobile-specific animations */ .mobile-slide-in { animation: slideInFromRight 0.3s ease; } @keyframes slideInFromRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } .mobile-fade-in { animation: fadeIn 0.5s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } /* Bottom sheet for mobile */ .bottom-sheet { position: fixed; bottom: 0; left: 0; right: 0; background: var(--white); border-top-left-radius: 20px; border-top-right-radius: 20px; box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1); transform: translateY(100%); transition: transform 0.3s ease; z-index: 1002; max-height: 80vh; overflow-y: auto; } .bottom-sheet.active { transform: translateY(0); } .bottom-sheet-handle { width: 40px; height: 4px; background: var(--gray); border-radius: 2px; margin: var(--space-md) auto var(--space-lg); } .bottom-sheet-content { padding: 0 var(--space-lg) var(--space-lg); } } /* Tablet-specific adjustments */ @media (min-width: 768px) and (max-width: 991px) { .mobile-only { display: none !important; } .tablet-cols-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); } .tablet-cols-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-md); } } /* Desktop view - hide mobile elements */ @media (min-width: 768px) { .mobile-only { display: none !important; } .desktop-only { display: block !important; } .mobile-quick-actions { display: none; } } -------------------- END OF FILE -------------------- FILE: assets/css/style.css TYPE: CSS SIZE: 11.6 KB ------------------------------------------------------------ /* Kayal Aqua - Main Stylesheet */ /* Mobile-First Responsive Design */ /* CSS Variables for consistent theming */ :root { /* Primary Colors */ --primary-navy: #1e3a5f; --primary-yellow: #ffc107; --primary-navy-light: #2c4a6b; --primary-navy-dark: #152d47; --primary-yellow-light: #fff350; --primary-yellow-dark: #e6ac00; /* Neutral Colors */ --white: #ffffff; --light-gray: #f8f9fa; --gray: #6c757d; --dark-gray: #495057; --black: #212529; /* Status Colors */ --success: #28a745; --danger: #dc3545; --warning: #ffc107; --info: #17a2b8; /* Spacing */ --space-xs: 0.25rem; --space-sm: 0.5rem; --space-md: 1rem; --space-lg: 1.5rem; --space-xl: 2rem; --space-xxl: 3rem; /* Typography */ --font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; --font-size-xs: 0.75rem; --font-size-sm: 0.875rem; --font-size-base: 1rem; --font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-xxl: 1.5rem; /* Border radius */ --border-radius: 0.375rem; --border-radius-lg: 0.5rem; /* Shadows */ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1); } /* Reset and Base Styles */ * { margin: 0; padding: 0; box-sizing: border-box; } html { font-size: 16px; scroll-behavior: smooth; } body { font-family: var(--font-family); font-size: var(--font-size-base); line-height: 1.6; color: var(--dark-gray); background-color: var(--light-gray); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Layout Components */ .container { width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 var(--space-md); } .main-wrapper { min-height: 100vh; display: flex; flex-direction: column; } .content-area { flex: 1; padding: var(--space-md); } /* Header Styles */ .header { background: linear-gradient(135deg, var(--primary-navy) 0%, var(--primary-navy-dark) 100%); color: var(--white); padding: var(--space-md) 0; box-shadow: var(--shadow-md); position: sticky; top: 0; z-index: 1000; } .header-content { display: flex; justify-content: space-between; align-items: center; } .logo { font-size: var(--font-size-xl); font-weight: bold; color: var(--primary-yellow); text-decoration: none; } .logo:hover { color: var(--primary-yellow-light); } /* Navigation */ .nav-toggle { display: block; background: none; border: none; color: var(--white); font-size: 1.5rem; cursor: pointer; padding: var(--space-sm); } .main-nav { position: fixed; top: 0; left: -100%; width: 280px; height: 100vh; background: var(--primary-navy); transition: left 0.3s ease; z-index: 1001; overflow-y: auto; } .main-nav.active { left: 0; } .nav-header { padding: var(--space-lg); background: var(--primary-navy-dark); display: flex; justify-content: space-between; align-items: center; } .nav-close { background: none; border: none; color: var(--white); font-size: 1.5rem; cursor: pointer; } .nav-menu { list-style: none; padding: var(--space-md) 0; } .nav-item { margin-bottom: var(--space-xs); } .nav-link { display: flex; align-items: center; padding: var(--space-md) var(--space-lg); color: var(--white); text-decoration: none; transition: all 0.3s ease; } .nav-link:hover, .nav-link.active { background: var(--primary-yellow); color: var(--primary-navy); } .nav-icon { margin-right: var(--space-md); width: 20px; } /* Overlay for mobile menu */ .nav-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; display: none; } .nav-overlay.active { display: block; } /* Card Components */ .card { background: var(--white); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); margin-bottom: var(--space-lg); overflow: hidden; } .card-header { padding: var(--space-lg); background: var(--primary-navy); color: var(--white); border-bottom: 1px solid var(--light-gray); } .card-title { font-size: var(--font-size-lg); font-weight: 600; margin: 0; } .card-body { padding: var(--space-lg); } .card-footer { padding: var(--space-lg); background: var(--light-gray); border-top: 1px solid #dee2e6; } /* Grid System */ .row { display: flex; flex-wrap: wrap; margin: 0 calc(var(--space-md) * -0.5); } .col { flex: 1; padding: 0 calc(var(--space-md) * 0.5); margin-bottom: var(--space-md); } .col-12 { flex: 0 0 100%; } .col-6 { flex: 0 0 50%; } .col-4 { flex: 0 0 33.333333%; } .col-3 { flex: 0 0 25%; } /* Form Elements */ .form-group { margin-bottom: var(--space-lg); } .form-label { display: block; margin-bottom: var(--space-sm); font-weight: 500; color: var(--primary-navy); } .form-control { width: 100%; padding: var(--space-md); border: 1px solid #ced4da; border-radius: var(--border-radius); font-size: var(--font-size-base); background: var(--white); transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; } .form-control:focus { outline: none; border-color: var(--primary-yellow); box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.25); } .form-select { appearance: none; background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); background-position: right 0.75rem center; background-repeat: no-repeat; background-size: 1.5em 1.5em; padding-right: 2.5rem; } /* Buttons */ .btn { display: inline-flex; align-items: center; justify-content: center; padding: var(--space-md) var(--space-lg); border: none; border-radius: var(--border-radius); font-size: var(--font-size-base); font-weight: 500; text-decoration: none; text-align: center; cursor: pointer; transition: all 0.3s ease; min-width: 44px; /* Accessibility - minimum touch target */ min-height: 44px; } .btn-primary { background: var(--primary-navy); color: var(--white); } .btn-primary:hover { background: var(--primary-navy-dark); color: var(--white); } .btn-secondary { background: var(--primary-yellow); color: var(--primary-navy); } .btn-secondary:hover { background: var(--primary-yellow-dark); color: var(--primary-navy); } .btn-success { background: var(--success); color: var(--white); } .btn-danger { background: var(--danger); color: var(--white); } .btn-outline { background: transparent; color: var(--primary-navy); border: 2px solid var(--primary-navy); } .btn-outline:hover { background: var(--primary-navy); color: var(--white); } .btn-sm { padding: var(--space-sm) var(--space-md); font-size: var(--font-size-sm); min-width: 36px; min-height: 36px; } .btn-lg { padding: var(--space-lg) var(--space-xl); font-size: var(--font-size-lg); } .btn-block { width: 100%; } /* Statistics Cards */ .stats-grid { display: grid; grid-template-columns: 1fr; gap: var(--space-md); margin-bottom: var(--space-xl); } .stat-card { background: var(--white); padding: var(--space-lg); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); text-align: center; transition: transform 0.3s ease, box-shadow 0.3s ease; } .stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); } .stat-value { font-size: var(--font-size-xxl); font-weight: bold; color: var(--primary-navy); display: block; } .stat-label { font-size: var(--font-size-sm); color: var(--gray); margin-top: var(--space-sm); } .stat-change { font-size: var(--font-size-xs); margin-top: var(--space-xs); } .stat-change.positive { color: var(--success); } .stat-change.negative { color: var(--danger); } /* Tables */ .table-responsive { overflow-x: auto; margin-bottom: var(--space-lg); } .table { width: 100%; margin-bottom: 0; background: var(--white); border-collapse: collapse; } .table th, .table td { padding: var(--space-md); text-align: left; border-bottom: 1px solid #dee2e6; } .table th { background: var(--primary-navy); color: var(--white); font-weight: 600; white-space: nowrap; } .table tbody tr:hover { background: var(--light-gray); } /* Utilities */ .text-center { text-align: center; } .text-right { text-align: right; } .text-left { text-align: left; } .text-primary { color: var(--primary-navy); } .text-secondary { color: var(--primary-yellow); } .text-success { color: var(--success); } .text-danger { color: var(--danger); } .text-warning { color: var(--warning); } .text-info { color: var(--info); } .text-muted { color: var(--gray); } .bg-primary { background-color: var(--primary-navy); } .bg-secondary { background-color: var(--primary-yellow); } .bg-light { background-color: var(--light-gray); } .d-none { display: none; } .d-block { display: block; } .d-flex { display: flex; } .d-inline-flex { display: inline-flex; } .justify-center { justify-content: center; } .justify-between { justify-content: space-between; } .justify-end { justify-content: flex-end; } .items-center { align-items: center; } .items-end { align-items: flex-end; } .m-0 { margin: 0; } .mt-1 { margin-top: var(--space-xs); } .mb-1 { margin-bottom: var(--space-xs); } .mt-2 { margin-top: var(--space-sm); } .mb-2 { margin-bottom: var(--space-sm); } .mt-3 { margin-top: var(--space-md); } .mb-3 { margin-bottom: var(--space-md); } .mt-4 { margin-top: var(--space-lg); } .mb-4 { margin-bottom: var(--space-lg); } .p-0 { padding: 0; } .p-1 { padding: var(--space-xs); } .p-2 { padding: var(--space-sm); } .p-3 { padding: var(--space-md); } .p-4 { padding: var(--space-lg); } /* Footer */ .footer { background: var(--primary-navy); color: var(--white); text-align: center; padding: var(--space-lg) 0; margin-top: auto; } /* Loading Spinner */ .spinner { width: 40px; height: 40px; border: 4px solid var(--light-gray); border-top: 4px solid var(--primary-yellow); border-radius: 50%; animation: spin 1s linear infinite; margin: var(--space-lg) auto; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Alert Messages */ .alert { padding: var(--space-md); margin-bottom: var(--space-lg); border: 1px solid transparent; border-radius: var(--border-radius); } .alert-success { color: #155724; background-color: #d4edda; border-color: #c3e6cb; } .alert-danger { color: #721c24; background-color: #f8d7da; border-color: #f5c6cb; } .alert-warning { color: #856404; background-color: #fff3cd; border-color: #ffeaa7; } .alert-info { color: #0c5460; background-color: #d1ecf1; border-color: #bee5eb; } /* Responsive Design */ @media (min-width: 576px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } } @media (min-width: 768px) { .container { padding: 0 var(--space-lg); } .content-area { padding: var(--space-xl); } .stats-grid { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } .nav-toggle { display: none; } .main-nav { position: static; width: auto; height: auto; background: transparent; display: flex; align-items: center; } .nav-header { display: none; } .nav-menu { display: flex; padding: 0; margin: 0; } .nav-item { margin-bottom: 0; margin-left: var(--space-md); } .nav-link { padding: var(--space-sm) var(--space-md); border-radius: var(--border-radius); } } @media (min-width: 992px) { .col-lg-12 { flex: 0 0 100%; } .col-lg-6 { flex: 0 0 50%; } .col-lg-4 { flex: 0 0 33.333333%; } .col-lg-3 { flex: 0 0 25%; } .col-lg-2 { flex: 0 0 16.666667%; } } -------------------- END OF FILE -------------------- FILE: assets/js/charts.js TYPE: JS SIZE: 18.48 KB ------------------------------------------------------------ /** * Charts and Data Visualization for Kayal Aqua * Mobile-responsive charts using Chart.js */ class KayalAquaCharts { constructor() { this.charts = {}; this.colors = { primary: '#1e3a5f', secondary: '#ffc107', success: '#28a745', danger: '#dc3545', warning: '#ffc107', info: '#17a2b8', light: '#f8f9fa', dark: '#495057' }; this.init(); } init() { // Initialize charts when DOM is ready if (typeof Chart !== 'undefined') { this.setupChartDefaults(); this.initializeAllCharts(); } else { // Load Chart.js if not already loaded this.loadChartJS().then(() => { this.setupChartDefaults(); this.initializeAllCharts(); }); } } async loadChartJS() { return new Promise((resolve, reject) => { if (typeof Chart !== 'undefined') { resolve(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.9.1/chart.min.js'; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } setupChartDefaults() { Chart.defaults.font.family = "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif"; Chart.defaults.color = this.colors.dark; Chart.defaults.responsive = true; Chart.defaults.maintainAspectRatio = false; // Mobile-specific defaults if (window.innerWidth <= 768) { Chart.defaults.font.size = 12; Chart.defaults.plugins.legend.labels.boxWidth = 12; Chart.defaults.plugins.legend.labels.padding = 10; } } initializeAllCharts() { // Revenue vs Expenses Chart this.initRevenueExpensesChart(); // Business Segments Pie Chart this.initBusinessSegmentsChart(); // Monthly Trends Line Chart this.initMonthlyTrendsChart(); // Cash Flow Chart this.initCashFlowChart(); // Top Products/Services Chart this.initTopProductsChart(); } initRevenueExpensesChart() { const canvas = document.getElementById('revenueExpensesChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Sample data - replace with actual data from API const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], datasets: [ { label: 'Revenue', data: [65000, 59000, 80000, 81000, 56000, 75000], backgroundColor: this.colors.secondary + '80', borderColor: this.colors.secondary, borderWidth: 2, fill: true }, { label: 'Expenses', data: [28000, 48000, 40000, 35000, 40000, 45000], backgroundColor: this.colors.danger + '80', borderColor: this.colors.danger, borderWidth: 2, fill: true } ] }; this.charts.revenueExpenses = new Chart(ctx, { type: 'line', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Revenue vs Expenses', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, tooltip: { callbacks: { label: function(context) { return context.dataset.label + ': ₹' + context.parsed.y.toLocaleString(); } } } }, interaction: { intersect: false, mode: 'index' } } }); } initBusinessSegmentsChart() { const canvas = document.getElementById('businessSegmentsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Fish Sales', 'Dry Fish Sales', 'Ornamental Fish', 'Fish Fry/Roast', 'Cutting Service'], datasets: [{ data: [35, 25, 15, 20, 5], backgroundColor: [ this.colors.primary, this.colors.secondary, this.colors.success, this.colors.info, this.colors.warning ], borderWidth: 2, borderColor: '#fff' }] }; this.charts.businessSegments = new Chart(ctx, { type: 'doughnut', data: data, options: { responsive: true, maintainAspectRatio: false, plugins: { title: { display: true, text: 'Revenue by Business Segment', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { position: window.innerWidth <= 768 ? 'bottom' : 'right', labels: { padding: window.innerWidth <= 768 ? 10 : 20, usePointStyle: true } }, tooltip: { callbacks: { label: function(context) { const total = context.dataset.data.reduce((a, b) => a + b, 0); const percentage = ((context.parsed / total) * 100).toFixed(1); return context.label + ': ' + percentage + '%'; } } } }, cutout: '60%' } }); } initMonthlyTrendsChart() { const canvas = document.getElementById('monthlyTrendsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], datasets: [ { label: 'Sales', data: [12, 19, 15, 25, 22, 30, 28, 35, 32, 38, 40, 45], borderColor: this.colors.primary, backgroundColor: this.colors.primary + '20', tension: 0.4, fill: true }, { label: 'Profit', data: [5, 8, 6, 12, 10, 15, 13, 18, 16, 20, 22, 25], borderColor: this.colors.success, backgroundColor: this.colors.success + '20', tension: 0.4, fill: true } ] }; this.charts.monthlyTrends = new Chart(ctx, { type: 'line', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + (value * 1000).toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Monthly Trends', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, tooltip: { callbacks: { label: function(context) { return context.dataset.label + ': ₹' + (context.parsed.y * 1000).toLocaleString(); } } } } } }); } initCashFlowChart() { const canvas = document.getElementById('cashFlowChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Week 1', 'Week 2', 'Week 3', 'Week 4'], datasets: [{ label: 'Cash Flow', data: [15000, -8000, 22000, -5000], backgroundColor: function(context) { const value = context.parsed.y; return value >= 0 ? '#28a745' : '#dc3545'; }, borderColor: function(context) { const value = context.parsed.y; return value >= 0 ? '#28a745' : '#dc3545'; }, borderWidth: 1 }] }; this.charts.cashFlow = new Chart(ctx, { type: 'bar', data: data, options: { responsive: true, maintainAspectRatio: false, scales: { y: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Weekly Cash Flow', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { display: false }, tooltip: { callbacks: { label: function(context) { const value = context.parsed.y; const type = value >= 0 ? 'Inflow' : 'Outflow'; return type + ': ₹' + Math.abs(value).toLocaleString(); } } } } } }); } initTopProductsChart() { const canvas = document.getElementById('topProductsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); const data = { labels: ['Fresh Pomfret', 'Dry Fish Mix', 'Gold Fish', 'Fish Fry', 'Cutting Service'], datasets: [{ label: 'Revenue', data: [25000, 18000, 12000, 15000, 8000], backgroundColor: [ this.colors.primary, this.colors.secondary, this.colors.success, this.colors.info, this.colors.warning ], borderWidth: 1, borderColor: '#fff' }] }; this.charts.topProducts = new Chart(ctx, { type: 'horizontalBar', data: data, options: { responsive: true, maintainAspectRatio: false, indexAxis: 'y', scales: { x: { beginAtZero: true, ticks: { callback: function(value) { return '₹' + value.toLocaleString(); } } } }, plugins: { title: { display: true, text: 'Top Products/Services', font: { size: 16, weight: 'bold' }, color: this.colors.primary }, legend: { display: false }, tooltip: { callbacks: { label: function(context) { return context.label + ': ₹' + context.parsed.x.toLocaleString(); } } } } } }); } // Utility method to update chart data updateChartData(chartName, newData) { if (this.charts[chartName]) { this.charts[chartName].data = newData; this.charts[chartName].update(); } } // Method to destroy a chart destroyChart(chartName) { if (this.charts[chartName]) { this.charts[chartName].destroy(); delete this.charts[chartName]; } } // Method to resize charts for mobile resizeChartsForMobile() { Object.values(this.charts).forEach(chart => { chart.resize(); }); } // Method to get chart data from API async loadChartData(endpoint) { try { const response = await fetch(`api/${endpoint}`); const data = await response.json(); return data; } catch (error) { console.error('Error loading chart data:', error); return null; } } // Method to refresh all charts with new data async refreshAllCharts() { // Show loading state document.querySelectorAll('.chart-container').forEach(container => { container.classList.add('loading'); }); try { // Load data for each chart const revenueExpensesData = await this.loadChartData('dashboard.php?action=revenue_expenses'); const businessSegmentsData = await this.loadChartData('dashboard.php?action=business_segments'); const monthlyTrendsData = await this.loadChartData('dashboard.php?action=monthly_trends'); const cashFlowData = await this.loadChartData('dashboard.php?action=cash_flow'); const topProductsData = await this.loadChartData('dashboard.php?action=top_products'); // Update charts with new data if (revenueExpensesData) this.updateChartData('revenueExpenses', revenueExpensesData); if (businessSegmentsData) this.updateChartData('businessSegments', businessSegmentsData); if (monthlyTrendsData) this.updateChartData('monthlyTrends', monthlyTrendsData); if (cashFlowData) this.updateChartData('cashFlow', cashFlowData); if (topProductsData) this.updateChartData('topProducts', topProductsData); } catch (error) { console.error('Error refreshing charts:', error); } finally { // Hide loading state document.querySelectorAll('.chart-container').forEach(container => { container.classList.remove('loading'); }); } } // Method to export chart as image exportChartAsImage(chartName, filename) { if (this.charts[chartName]) { const link = document.createElement('a'); link.download = filename || `${chartName}_chart.png`; link.href = this.charts[chartName].toBase64Image(); link.click(); } } // Method to create mini dashboard charts for mobile createMiniCharts() { const miniChartContainers = document.querySelectorAll('.mini-chart'); miniChartContainers.forEach((container, index) => { const canvas = container.querySelector('canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); // Sample data for mini charts const miniData = { labels: ['', '', '', '', ''], datasets: [{ data: [12, 19, 15, 25, 22], borderColor: this.colors.secondary, backgroundColor: this.colors.secondary + '40', borderWidth: 2, fill: true, tension: 0.4, pointRadius: 0 }] }; new Chart(ctx, { type: 'line', data: miniData, options: { responsive: true, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false } }, plugins: { legend: { display: false }, tooltip: { enabled: false } }, elements: { point: { radius: 0 } } } }); }); } } // Initialize charts when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.kayalAquaCharts = new KayalAquaCharts(); }); // Handle window resize for chart responsiveness window.addEventListener('resize', () => { if (window.kayalAquaCharts) { window.kayalAquaCharts.resizeChartsForMobile(); } }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = KayalAquaCharts; } // Global function to initialize charts (called from main.js) function initializeCharts() { if (window.kayalAquaCharts) { window.kayalAquaCharts.refreshAllCharts(); } } -------------------- END OF FILE -------------------- FILE: assets/js/main.js TYPE: JS SIZE: 17.76 KB ------------------------------------------------------------ /** * Main JavaScript for Kayal Aqua Fish Business Management * Mobile-First Responsive Application */ class KayalAquaApp { constructor() { this.init(); } init() { this.setupEventListeners(); this.initializeComponents(); this.handleResponsiveLayout(); } setupEventListeners() { // Navigation toggle document.addEventListener('click', (e) => { if (e.target.matches('.nav-toggle') || e.target.closest('.nav-toggle')) { this.toggleMobileNav(); } if (e.target.matches('.nav-close') || e.target.closest('.nav-close')) { this.closeMobileNav(); } if (e.target.matches('.nav-overlay')) { this.closeMobileNav(); } // Modal handlers if (e.target.matches('[data-modal]')) { this.openModal(e.target.dataset.modal); } if (e.target.matches('.modal-close') || e.target.matches('.modal-overlay')) { this.closeModal(); } // Dropdown handlers if (e.target.matches('.dropdown-toggle') || e.target.closest('.dropdown-toggle')) { this.toggleDropdown(e.target.closest('.dropdown')); } // Tab handlers if (e.target.matches('.tab-nav-item')) { this.switchTab(e.target); } // Accordion handlers if (e.target.matches('.accordion-header') || e.target.closest('.accordion-header')) { this.toggleAccordion(e.target.closest('.accordion-header')); } }); // Close dropdowns when clicking outside document.addEventListener('click', (e) => { if (!e.target.closest('.dropdown')) { this.closeAllDropdowns(); } }); // Form submissions document.addEventListener('submit', (e) => { if (e.target.matches('form[data-ajax]')) { e.preventDefault(); this.handleAjaxForm(e.target); } }); // Window resize handler window.addEventListener('resize', () => { this.handleResponsiveLayout(); }); // Scroll handlers for mobile let lastScrollTop = 0; window.addEventListener('scroll', () => { const scrollTop = window.pageYOffset || document.documentElement.scrollTop; // Hide/show header on scroll (mobile) if (window.innerWidth <= 768) { const header = document.querySelector('.header'); if (header) { if (scrollTop > lastScrollTop && scrollTop > 100) { header.style.transform = 'translateY(-100%)'; } else { header.style.transform = 'translateY(0)'; } } } lastScrollTop = scrollTop <= 0 ? 0 : scrollTop; }); } initializeComponents() { // Initialize tooltips this.initializeTooltips(); // Initialize form validation this.initializeFormValidation(); // Initialize data tables this.initializeDataTables(); // Initialize charts if needed this.initializeCharts(); // Set active navigation this.setActiveNavigation(); } // Navigation Methods toggleMobileNav() { const nav = document.querySelector('.main-nav'); const overlay = document.querySelector('.nav-overlay'); if (nav && overlay) { nav.classList.toggle('active'); overlay.classList.toggle('active'); document.body.style.overflow = nav.classList.contains('active') ? 'hidden' : ''; } } closeMobileNav() { const nav = document.querySelector('.main-nav'); const overlay = document.querySelector('.nav-overlay'); if (nav && overlay) { nav.classList.remove('active'); overlay.classList.remove('active'); document.body.style.overflow = ''; } } setActiveNavigation() { const currentPage = window.location.pathname.split('/').pop() || 'index.php'; const navLinks = document.querySelectorAll('.nav-link'); navLinks.forEach(link => { const href = link.getAttribute('href'); if (href === currentPage || (currentPage === '' && href === 'index.php')) { link.classList.add('active'); } else { link.classList.remove('active'); } }); } // Modal Methods openModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.classList.add('active'); document.body.style.overflow = 'hidden'; } } closeModal() { const activeModal = document.querySelector('.modal.active'); if (activeModal) { activeModal.classList.remove('active'); document.body.style.overflow = ''; } } // Dropdown Methods toggleDropdown(dropdown) { if (dropdown) { dropdown.classList.toggle('active'); } } closeAllDropdowns() { const dropdowns = document.querySelectorAll('.dropdown.active'); dropdowns.forEach(dropdown => { dropdown.classList.remove('active'); }); } // Tab Methods switchTab(tabButton) { const tabContainer = tabButton.closest('.tabs'); if (!tabContainer) return; const targetTab = tabButton.dataset.tab; // Remove active class from all tabs and contents tabContainer.querySelectorAll('.tab-nav-item').forEach(tab => { tab.classList.remove('active'); }); tabContainer.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); // Add active class to clicked tab and corresponding content tabButton.classList.add('active'); const targetContent = tabContainer.querySelector(`#${targetTab}`); if (targetContent) { targetContent.classList.add('active'); } } // Accordion Methods toggleAccordion(header) { const content = header.nextElementSibling; const icon = header.querySelector('.accordion-icon'); if (content) { header.classList.toggle('active'); content.classList.toggle('active'); if (icon) { icon.style.transform = content.classList.contains('active') ? 'rotate(180deg)' : 'rotate(0deg)'; } } } // Form Methods handleAjaxForm(form) { const formData = new FormData(form); const url = form.action || window.location.href; this.showLoader(); fetch(url, { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { this.hideLoader(); if (data.success) { this.showAlert(data.message || 'Operation completed successfully', 'success'); if (data.redirect) { setTimeout(() => { window.location.href = data.redirect; }, 1500); } if (data.reload) { setTimeout(() => { window.location.reload(); }, 1500); } } else { this.showAlert(data.message || 'An error occurred', 'danger'); } }) .catch(error => { this.hideLoader(); this.showAlert('Network error occurred', 'danger'); console.error('Error:', error); }); } initializeFormValidation() { const forms = document.querySelectorAll('form[data-validate]'); forms.forEach(form => { form.addEventListener('submit', (e) => { if (!this.validateForm(form)) { e.preventDefault(); } }); // Real-time validation const inputs = form.querySelectorAll('input, select, textarea'); inputs.forEach(input => { input.addEventListener('blur', () => { this.validateField(input); }); }); }); } validateForm(form) { let isValid = true; const inputs = form.querySelectorAll('[required], [data-validate]'); inputs.forEach(input => { if (!this.validateField(input)) { isValid = false; } }); return isValid; } validateField(field) { const value = field.value.trim(); const type = field.type; let isValid = true; let message = ''; // Clear previous errors this.clearFieldError(field); // Required validation if (field.hasAttribute('required') && !value) { isValid = false; message = 'This field is required'; } // Email validation if (type === 'email' && value && !this.isValidEmail(value)) { isValid = false; message = 'Please enter a valid email address'; } // Phone validation if (field.dataset.validate === 'phone' && value && !this.isValidPhone(value)) { isValid = false; message = 'Please enter a valid phone number'; } // Number validation if (type === 'number' && value) { const min = parseFloat(field.min); const max = parseFloat(field.max); const numValue = parseFloat(value); if (!isNaN(min) && numValue < min) { isValid = false; message = `Value must be at least ${min}`; } if (!isNaN(max) && numValue > max) { isValid = false; message = `Value must be no more than ${max}`; } } if (!isValid) { this.showFieldError(field, message); } return isValid; } showFieldError(field, message) { field.classList.add('is-invalid'); let errorDiv = field.parentNode.querySelector('.invalid-feedback'); if (!errorDiv) { errorDiv = document.createElement('div'); errorDiv.className = 'invalid-feedback'; field.parentNode.appendChild(errorDiv); } errorDiv.textContent = message; } clearFieldError(field) { field.classList.remove('is-invalid'); const errorDiv = field.parentNode.querySelector('.invalid-feedback'); if (errorDiv) { errorDiv.remove(); } } // Utility Methods isValidEmail(email) { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); } isValidPhone(phone) { const phoneRegex = /^[+]?[0-9\s\-\(\)]{10,}$/; return phoneRegex.test(phone); } showAlert(message, type = 'info', duration = 5000) { const alertContainer = document.getElementById('alert-container') || this.createAlertContainer(); const alert = document.createElement('div'); alert.className = `alert alert-${type} alert-dismissible`; alert.innerHTML = ` ${message} `; alertContainer.appendChild(alert); // Auto remove after duration if (duration > 0) { setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, duration); } } createAlertContainer() { const container = document.createElement('div'); container.id = 'alert-container'; container.className = 'alert-container'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px; `; document.body.appendChild(container); return container; } showLoader() { let loader = document.getElementById('app-loader'); if (!loader) { loader = document.createElement('div'); loader.id = 'app-loader'; loader.innerHTML = '
'; loader.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(255, 255, 255, 0.8); display: flex; justify-content: center; align-items: center; z-index: 9999; `; document.body.appendChild(loader); } loader.style.display = 'flex'; } hideLoader() { const loader = document.getElementById('app-loader'); if (loader) { loader.style.display = 'none'; } } initializeTooltips() { // Tooltip functionality is handled by CSS :hover // This method can be extended for more complex tooltip behavior } initializeDataTables() { const tables = document.querySelectorAll('.data-table[data-sortable]'); tables.forEach(table => { this.makeTableSortable(table); }); } makeTableSortable(table) { const headers = table.querySelectorAll('th[data-sortable]'); headers.forEach(header => { header.style.cursor = 'pointer'; header.addEventListener('click', () => { this.sortTable(table, header); }); }); } sortTable(table, header) { const tbody = table.querySelector('tbody'); const rows = Array.from(tbody.querySelectorAll('tr')); const columnIndex = Array.from(header.parentNode.children).indexOf(header); const isAscending = !header.classList.contains('sort-asc'); // Clear all sort classes header.parentNode.querySelectorAll('th').forEach(th => { th.classList.remove('sort-asc', 'sort-desc'); }); // Add sort class to current header header.classList.add(isAscending ? 'sort-asc' : 'sort-desc'); // Sort rows rows.sort((a, b) => { const aValue = a.children[columnIndex].textContent.trim(); const bValue = b.children[columnIndex].textContent.trim(); // Try to parse as numbers const aNum = parseFloat(aValue.replace(/[^\d.-]/g, '')); const bNum = parseFloat(bValue.replace(/[^\d.-]/g, '')); if (!isNaN(aNum) && !isNaN(bNum)) { return isAscending ? aNum - bNum : bNum - aNum; } // String comparison return isAscending ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); }); // Reorder DOM rows.forEach(row => tbody.appendChild(row)); } initializeCharts() { // Chart initialization will be handled in charts.js // This method triggers chart initialization if charts.js is loaded if (typeof initializeCharts === 'function') { initializeCharts(); } } handleResponsiveLayout() { const isMobile = window.innerWidth <= 768; // Handle table responsiveness const tables = document.querySelectorAll('.table'); tables.forEach(table => { if (isMobile) { table.classList.add('table-mobile'); } else { table.classList.remove('table-mobile'); } }); // Close mobile nav on resize to desktop if (!isMobile) { this.closeMobileNav(); } } // API Methods async apiCall(url, method = 'GET', data = null) { const options = { method, headers: { 'Content-Type': 'application/json', } }; if (data) { options.body = JSON.stringify(data); } try { const response = await fetch(url, options); return await response.json(); } catch (error) { console.error('API Error:', error); throw error; } } // Format currency for display formatCurrency(amount) { return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 2 }).format(amount); } // Format date for display formatDate(dateString, options = {}) { const defaultOptions = { year: 'numeric', month: 'short', day: 'numeric' }; return new Date(dateString).toLocaleDateString('en-IN', { ...defaultOptions, ...options }); } // Debounce function for search inputs debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } } // Initialize the application when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.kayalAquaApp = new KayalAquaApp(); }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = KayalAquaApp; } -------------------- END OF FILE -------------------- FILE: assets/js/mobile.js TYPE: JS SIZE: 19.07 KB ------------------------------------------------------------ /** * Mobile-specific JavaScript for Kayal Aqua * Enhanced mobile user experience */ class MobileEnhancements { constructor() { this.init(); } init() { if (this.isMobileDevice()) { this.setupMobileFeatures(); this.setupTouchGestures(); this.setupMobileNavigation(); this.setupPullToRefresh(); this.setupMobileSearch(); this.setupBottomSheet(); this.setupQuickActions(); } } isMobileDevice() { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth <= 768; } setupMobileFeatures() { // Prevent zoom on input focus this.preventInputZoom(); // Handle orientation change window.addEventListener('orientationchange', () => { setTimeout(() => { this.handleOrientationChange(); }, 100); }); // Handle viewport changes this.handleViewportChanges(); // Setup swipe gestures for navigation this.setupSwipeNavigation(); // Mobile-specific form enhancements this.enhanceMobileForms(); } preventInputZoom() { // Add viewport meta tag if not present let viewport = document.querySelector('meta[name="viewport"]'); if (!viewport) { viewport = document.createElement('meta'); viewport.name = 'viewport'; document.head.appendChild(viewport); } viewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; // Ensure input font-size is at least 16px to prevent zoom const inputs = document.querySelectorAll('input, select, textarea'); inputs.forEach(input => { if (getComputedStyle(input).fontSize < '16px') { input.style.fontSize = '16px'; } }); } handleOrientationChange() { // Refresh layout after orientation change document.body.scrollTop = 0; document.documentElement.scrollTop = 0; // Trigger resize event window.dispatchEvent(new Event('resize')); // Close any open mobile menus if (window.kayalAquaApp) { window.kayalAquaApp.closeMobileNav(); } } handleViewportChanges() { let vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); window.addEventListener('resize', () => { vh = window.innerHeight * 0.01; document.documentElement.style.setProperty('--vh', `${vh}px`); }); } setupTouchGestures() { let startX, startY, endX, endY; document.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; }, { passive: true }); document.addEventListener('touchend', (e) => { if (!startX || !startY) return; endX = e.changedTouches[0].clientX; endY = e.changedTouches[0].clientY; const deltaX = startX - endX; const deltaY = startY - endY; // Only handle horizontal swipes that are significant if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > 100) { if (deltaX > 0) { // Swipe left this.handleSwipeLeft(); } else { // Swipe right this.handleSwipeRight(); } } // Reset values startX = startY = endX = endY = null; }, { passive: true }); } handleSwipeLeft() { // Close mobile nav if open const nav = document.querySelector('.main-nav.active'); if (nav && window.kayalAquaApp) { window.kayalAquaApp.closeMobileNav(); } } handleSwipeRight() { // Open mobile nav if closed const nav = document.querySelector('.main-nav'); if (nav && !nav.classList.contains('active') && window.kayalAquaApp) { window.kayalAquaApp.toggleMobileNav(); } } setupSwipeNavigation() { // Add swipe indicators to swipeable elements const swipeContainers = document.querySelectorAll('.swipe-container'); swipeContainers.forEach(container => { container.style.scrollSnapType = 'x mandatory'; const items = container.querySelectorAll('.swipe-item'); items.forEach(item => { item.style.scrollSnapAlign = 'start'; }); }); } setupMobileNavigation() { // Add touch-friendly navigation const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('touchstart', function() { this.style.backgroundColor = 'rgba(255, 193, 7, 0.1)'; }, { passive: true }); item.addEventListener('touchend', function() { setTimeout(() => { this.style.backgroundColor = ''; }, 150); }, { passive: true }); }); } setupPullToRefresh() { let startY = 0; let currentY = 0; let pullThreshold = 100; let isRefreshing = false; const refreshIndicator = this.createRefreshIndicator(); document.addEventListener('touchstart', (e) => { if (window.scrollY === 0) { startY = e.touches[0].clientY; } }, { passive: true }); document.addEventListener('touchmove', (e) => { if (isRefreshing || window.scrollY > 0) return; currentY = e.touches[0].clientY; const pullDistance = currentY - startY; if (pullDistance > 0) { const pullRatio = Math.min(pullDistance / pullThreshold, 1); refreshIndicator.style.transform = `translateY(${pullDistance * 0.5}px)`; refreshIndicator.style.opacity = pullRatio; if (pullDistance > pullThreshold) { refreshIndicator.textContent = 'Release to refresh'; refreshIndicator.classList.add('ready-to-refresh'); } else { refreshIndicator.textContent = 'Pull to refresh'; refreshIndicator.classList.remove('ready-to-refresh'); } } }, { passive: true }); document.addEventListener('touchend', () => { if (isRefreshing) return; const pullDistance = currentY - startY; if (pullDistance > pullThreshold) { this.triggerRefresh(refreshIndicator); } else { this.resetRefreshIndicator(refreshIndicator); } startY = 0; currentY = 0; }, { passive: true }); } createRefreshIndicator() { const indicator = document.createElement('div'); indicator.className = 'pull-refresh'; indicator.textContent = 'Pull to refresh'; indicator.style.cssText = ` position: fixed; top: -50px; left: 0; right: 0; height: 50px; background: var(--primary-navy); color: var(--white); display: flex; align-items: center; justify-content: center; font-size: 14px; opacity: 0; transform: translateY(0); transition: transform 0.3s ease, opacity 0.3s ease; z-index: 1000; `; document.body.appendChild(indicator); return indicator; } triggerRefresh(indicator) { indicator.textContent = 'Refreshing...'; indicator.style.transform = 'translateY(50px)'; indicator.style.opacity = '1'; // Simulate refresh - replace with actual refresh logic setTimeout(() => { window.location.reload(); }, 1500); } resetRefreshIndicator(indicator) { indicator.style.transform = 'translateY(0)'; indicator.style.opacity = '0'; indicator.textContent = 'Pull to refresh'; indicator.classList.remove('ready-to-refresh'); } setupMobileSearch() { const searchInputs = document.querySelectorAll('.mobile-search-input'); searchInputs.forEach(input => { const searchBtn = input.parentElement.querySelector('.mobile-search-btn'); input.addEventListener('input', this.debounce((e) => { const query = e.target.value.trim(); if (query.length > 2) { this.performMobileSearch(query); } }, 300)); if (searchBtn) { searchBtn.addEventListener('click', () => { const query = input.value.trim(); if (query) { this.performMobileSearch(query); } }); } }); } performMobileSearch(query) { // Implement search functionality based on current page const currentPage = window.location.pathname.split('/').pop(); console.log(`Searching for: ${query} on page: ${currentPage}`); // Show search results this.showSearchResults(query); } showSearchResults(query) { // Create or update search results container let resultsContainer = document.getElementById('mobile-search-results'); if (!resultsContainer) { resultsContainer = document.createElement('div'); resultsContainer.id = 'mobile-search-results'; resultsContainer.className = 'mobile-search-results'; resultsContainer.style.cssText = ` position: absolute; top: 100%; left: 0; right: 0; background: white; border: 1px solid #ddd; border-top: none; border-radius: 0 0 8px 8px; max-height: 300px; overflow-y: auto; z-index: 1000; display: none; `; const searchContainer = document.querySelector('.mobile-search'); if (searchContainer) { searchContainer.style.position = 'relative'; searchContainer.appendChild(resultsContainer); } } // Show loading state resultsContainer.innerHTML = '
Searching...
'; resultsContainer.style.display = 'block'; // Here you would make an API call to get search results // For now, showing placeholder setTimeout(() => { resultsContainer.innerHTML = `
Sample Result
Search results for "${query}" would appear here
`; }, 500); } setupBottomSheet() { const bottomSheets = document.querySelectorAll('.bottom-sheet'); bottomSheets.forEach(sheet => { const handle = sheet.querySelector('.bottom-sheet-handle'); if (handle) { let startY = 0; let currentY = 0; let isDragging = false; handle.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; isDragging = true; }, { passive: true }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; currentY = e.touches[0].clientY; const deltaY = currentY - startY; if (deltaY > 0) { sheet.style.transform = `translateY(${deltaY}px)`; } }, { passive: true }); document.addEventListener('touchend', () => { if (!isDragging) return; const deltaY = currentY - startY; if (deltaY > 100) { this.closeBottomSheet(sheet); } else { sheet.style.transform = 'translateY(0)'; } isDragging = false; startY = 0; currentY = 0; }, { passive: true }); } }); } openBottomSheet(sheetId) { const sheet = document.getElementById(sheetId); if (sheet) { sheet.classList.add('active'); document.body.style.overflow = 'hidden'; } } closeBottomSheet(sheet) { if (sheet) { sheet.classList.remove('active'); sheet.style.transform = ''; document.body.style.overflow = ''; } } setupQuickActions() { // Create quick action button if it doesn't exist let quickActions = document.querySelector('.mobile-quick-actions'); if (!quickActions) { quickActions = document.createElement('div'); quickActions.className = 'mobile-quick-actions'; // Add quick action buttons based on current page const currentPage = window.location.pathname.split('/').pop(); this.addQuickActionsForPage(quickActions, currentPage); document.body.appendChild(quickActions); } } addQuickActionsForPage(container, page) { const actions = this.getQuickActionsForPage(page); actions.forEach(action => { const btn = document.createElement('button'); btn.className = 'quick-action-btn'; btn.innerHTML = action.icon; btn.title = action.title; btn.onclick = action.onclick; container.appendChild(btn); }); } getQuickActionsForPage(page) { const actions = { 'dashboard.php': [ { icon: '+', title: 'Add Transaction', onclick: () => this.showQuickAddModal() } ], 'sales.php': [ { icon: '+', title: 'Add Sale', onclick: () => window.location.href = 'pages/sales.php#add' } ], 'expenses.php': [ { icon: '+', title: 'Add Expense', onclick: () => window.location.href = 'pages/expenses.php#add' } ] }; return actions[page] || []; } showQuickAddModal() { // Create quick add modal for mobile const modal = document.createElement('div'); modal.className = 'modal active'; modal.innerHTML = ` `; document.body.appendChild(modal); // Handle modal close modal.querySelector('.modal-close').onclick = () => { modal.remove(); }; modal.onclick = (e) => { if (e.target === modal) { modal.remove(); } }; } enhanceMobileForms() { // Add better mobile form experience const forms = document.querySelectorAll('form'); forms.forEach(form => { // Add floating labels for mobile const inputs = form.querySelectorAll('input:not([type="hidden"]), textarea, select'); inputs.forEach(input => { if (input.placeholder && !input.labels.length) { this.createFloatingLabel(input); } }); // Auto-scroll to validation errors on mobile form.addEventListener('submit', (e) => { setTimeout(() => { const firstError = form.querySelector('.is-invalid'); if (firstError) { firstError.scrollIntoView({ behavior: 'smooth', block: 'center' }); } }, 100); }); }); } createFloatingLabel(input) { const wrapper = document.createElement('div'); wrapper.className = 'mobile-form-group'; input.parentNode.insertBefore(wrapper, input); wrapper.appendChild(input); const label = document.createElement('label'); label.textContent = input.placeholder; label.className = 'form-label'; wrapper.appendChild(label); input.placeholder = ''; } debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Haptic feedback for supported devices vibrate(pattern = [100]) { if ('vibrate' in navigator) { navigator.vibrate(pattern); } } // Add vibration feedback to buttons addHapticFeedback() { const buttons = document.querySelectorAll('button, .btn'); buttons.forEach(button => { button.addEventListener('touchstart', () => { this.vibrate([10]); }, { passive: true }); }); } } // Initialize mobile enhancements when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.mobileEnhancements = new MobileEnhancements(); }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = MobileEnhancements; } -------------------- END OF FILE -------------------- FILE: config/config.php TYPE: PHP SIZE: 3.85 KB ------------------------------------------------------------ $_SESSION['user_id'], 'action' => $action, 'description' => $description, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; // Create activity log table if it doesn't exist $sql = "CREATE TABLE IF NOT EXISTS activity_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, action VARCHAR(100), description TEXT, ip_address VARCHAR(45), user_agent TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) )"; try { executeQuery($sql); insertData('activity_logs', $data); } catch (Exception $e) { error_log("Failed to log activity: " . $e->getMessage()); } } ?> -------------------- END OF FILE -------------------- FILE: config/database.php TYPE: PHP SIZE: 3.94 KB ------------------------------------------------------------ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci" ] ); return $pdo; } catch (PDOException $e) { // Log error with more details error_log("Database connection failed: " . $e->getMessage()); // Show detailed error for debugging (remove in production) die("

Database Connection Failed

Error: " . htmlspecialchars($e->getMessage()) . "

Please check:

Update the DB_PASS in config/database.php with your actual database password.

"); } } // Initialize database connection $pdo = initializeDatabase(); /** * Execute a prepared statement with parameters */ function executeQuery($sql, $params = []) { global $pdo; if ($pdo === null) { $pdo = initializeDatabase(); } try { $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt; } catch (PDOException $e) { error_log("Query execution failed: " . $e->getMessage() . " | SQL: " . $sql); // Check if it's a "table doesn't exist" error during setup if (strpos($e->getMessage(), "doesn't exist") !== false && basename($_SERVER['PHP_SELF']) === 'setup.php') { // This is expected during setup - tables need to be created return false; } throw new Exception("Database operation failed: " . $e->getMessage()); } } /** * Get single row from database */ function fetchRow($sql, $params = []) { $stmt = executeQuery($sql, $params); return $stmt->fetch(); } /** * Get all rows from database */ function fetchAll($sql, $params = []) { $stmt = executeQuery($sql, $params); return $stmt->fetchAll(); } /** * Insert data and return last insert ID */ function insertData($table, $data) { global $pdo; $columns = array_keys($data); $placeholders = array_map(function($col) { return ':' . $col; }, $columns); $sql = "INSERT INTO {$table} (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")"; $stmt = executeQuery($sql, $data); return $pdo->lastInsertId(); } /** * Update data in database */ function updateData($table, $data, $where, $whereParams = []) { $setParts = array_map(function($col) { return $col . ' = :' . $col; }, array_keys($data)); $sql = "UPDATE {$table} SET " . implode(', ', $setParts) . " WHERE {$where}"; $params = array_merge($data, $whereParams); return executeQuery($sql, $params); } ?> -------------------- END OF FILE -------------------- FILE: includes/footer.php TYPE: PHP SIZE: 8.61 KB ------------------------------------------------------------
-------------------- END OF FILE -------------------- FILE: includes/functions.php TYPE: PHP SIZE: 17.01 KB ------------------------------------------------------------ MAX_FILE_SIZE) { throw new Exception('File too large. Maximum size: ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'); } // Create upload directory if it doesn't exist if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } // Generate unique filename $filename = generateUniqueFilename($file['name']); $filepath = $uploadDir . $filename; // Move uploaded file if (!move_uploaded_file($file['tmp_name'], $filepath)) { throw new Exception('Failed to save uploaded file'); } return $filepath; } /** * Get dashboard statistics */ function getDashboardStats($userId = null) { // Total Revenue $revenueQuery = "SELECT COALESCE(SUM(amount), 0) as total_revenue FROM sales WHERE 1=1"; $params = []; if ($userId) { $revenueQuery .= " AND created_by = ?"; $params[] = $userId; } $totalRevenue = fetchRow($revenueQuery, $params)['total_revenue']; // Total Expenses $expenseQuery = "SELECT COALESCE(SUM(amount), 0) as total_expenses FROM expenses WHERE 1=1"; $expenseParams = []; if ($userId) { $expenseQuery .= " AND created_by = ?"; $expenseParams[] = $userId; } $totalExpenses = fetchRow($expenseQuery, $expenseParams)['total_expenses']; // Monthly Revenue (current month) $monthlyRevenueQuery = "SELECT COALESCE(SUM(amount), 0) as monthly_revenue FROM sales WHERE MONTH(sale_date) = MONTH(CURRENT_DATE()) AND YEAR(sale_date) = YEAR(CURRENT_DATE())"; $monthlyParams = []; if ($userId) { $monthlyRevenueQuery .= " AND created_by = ?"; $monthlyParams[] = $userId; } $monthlyRevenue = fetchRow($monthlyRevenueQuery, $monthlyParams)['monthly_revenue']; // Active Investments/Loans $investmentQuery = "SELECT COALESCE(SUM(amount), 0) as total_investments FROM investments WHERE status = 'active'"; $totalInvestments = fetchRow($investmentQuery)['total_investments']; // Profit Calculation $profit = $totalRevenue - $totalExpenses; $profitMargin = $totalRevenue > 0 ? ($profit / $totalRevenue) * 100 : 0; return [ 'total_revenue' => $totalRevenue, 'total_expenses' => $totalExpenses, 'monthly_revenue' => $monthlyRevenue, 'total_investments' => $totalInvestments, 'profit' => $profit, 'profit_margin' => $profitMargin ]; } /** * Get recent transactions for dashboard */ function getRecentTransactions($limit = 10, $userId = null) { $query = " (SELECT 'sale' as type, id, title, amount, sale_date as transaction_date, customer_name as party_name, created_at FROM sales WHERE 1=1" . ($userId ? " AND created_by = ?" : "") . ") UNION ALL (SELECT 'expense' as type, id, title, amount, expense_date as transaction_date, '' as party_name, created_at FROM expenses WHERE 1=1" . ($userId ? " AND created_by = ?" : "") . ") ORDER BY transaction_date DESC, created_at DESC LIMIT ?"; $params = []; if ($userId) { $params[] = $userId; $params[] = $userId; } $params[] = $limit; return fetchAll($query, $params); } /** * Get business segment performance */ function getSegmentPerformance() { $query = "SELECT bs.name as segment_name, COALESCE(SUM(s.amount), 0) as revenue, COALESCE(SUM(e.amount), 0) as expenses, (COALESCE(SUM(s.amount), 0) - COALESCE(SUM(e.amount), 0)) as profit FROM business_segments bs LEFT JOIN sales s ON bs.id = s.segment_id LEFT JOIN expenses e ON bs.id = e.segment_id WHERE bs.status = 'active' GROUP BY bs.id, bs.name ORDER BY revenue DESC"; return fetchAll($query); } /** * Get monthly trends data for charts */ function getMonthlyTrends($year = null) { if (!$year) { $year = date('Y'); } $query = "SELECT MONTH(t.transaction_date) as month, MONTHNAME(t.transaction_date) as month_name, SUM(CASE WHEN t.type = 'sale' THEN t.amount ELSE 0 END) as revenue, SUM(CASE WHEN t.type = 'expense' THEN t.amount ELSE 0 END) as expenses FROM ( SELECT 'sale' as type, amount, sale_date as transaction_date FROM sales UNION ALL SELECT 'expense' as type, amount, expense_date as transaction_date FROM expenses ) t WHERE YEAR(t.transaction_date) = ? GROUP BY MONTH(t.transaction_date), MONTHNAME(t.transaction_date) ORDER BY MONTH(t.transaction_date)"; return fetchAll($query, [$year]); } /** * Search function for sales, expenses, etc. */ function performSearch($query, $table, $searchFields, $userId = null, $limit = 50) { $searchConditions = []; $params = []; // Build search conditions foreach ($searchFields as $field) { $searchConditions[] = "$field LIKE ?"; $params[] = "%$query%"; } $sql = "SELECT * FROM $table WHERE (" . implode(' OR ', $searchConditions) . ")"; // Add user filter if provided if ($userId) { $sql .= " AND created_by = ?"; $params[] = $userId; } $sql .= " ORDER BY created_at DESC LIMIT ?"; $params[] = $limit; return fetchAll($sql, $params); } /** * Generate report data */ function generateReport($type, $startDate, $endDate, $segmentId = null) { $params = [$startDate, $endDate]; switch ($type) { case 'sales': $query = "SELECT s.*, bs.name as segment_name, c.name as category_name FROM sales s LEFT JOIN business_segments bs ON s.segment_id = bs.id LEFT JOIN categories c ON s.category_id = c.id WHERE s.sale_date BETWEEN ? AND ?"; break; case 'expenses': $query = "SELECT e.*, bs.name as segment_name, c.name as category_name FROM expenses e LEFT JOIN business_segments bs ON e.segment_id = bs.id LEFT JOIN categories c ON e.category_id = c.id WHERE e.expense_date BETWEEN ? AND ?"; break; case 'profit_loss': $query = "SELECT 'Revenue' as type, SUM(amount) as total_amount, COUNT(*) as transaction_count FROM sales WHERE sale_date BETWEEN ? AND ? UNION ALL SELECT 'Expenses' as type, SUM(amount) as total_amount, COUNT(*) as transaction_count FROM expenses WHERE expense_date BETWEEN ? AND ?"; $params = [$startDate, $endDate, $startDate, $endDate]; break; default: throw new Exception('Invalid report type'); } // Add segment filter if provided if ($segmentId && in_array($type, ['sales', 'expenses'])) { $query .= " AND segment_id = ?"; $params[] = $segmentId; } if ($type !== 'profit_loss') { $query .= " ORDER BY " . ($type === 'sales' ? 'sale_date' : 'expense_date') . " DESC"; } return fetchAll($query, $params); } /** * Export data to CSV */ function exportToCSV($data, $filename, $headers = null) { header('Content-Type: text/csv'); header('Content-Disposition: attachment; filename="' . $filename . '"'); header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); $output = fopen('php://output', 'w'); // Add headers if provided or use array keys from first row if ($headers) { fputcsv($output, $headers); } elseif (!empty($data)) { fputcsv($output, array_keys($data[0])); } // Add data rows foreach ($data as $row) { fputcsv($output, $row); } fclose($output); exit; } /** * Calculate loan EMI */ function calculateEMI($principal, $rate, $tenure) { $monthlyRate = $rate / (12 * 100); $emi = ($principal * $monthlyRate * pow(1 + $monthlyRate, $tenure)) / (pow(1 + $monthlyRate, $tenure) - 1); return round($emi, 2); } /** * Send email notification (basic implementation) */ function sendEmail($to, $subject, $message, $isHTML = true) { $headers = [ 'From: ' . APP_NAME . ' ', 'Reply-To: noreply@' . parse_url(APP_URL, PHP_URL_HOST), 'X-Mailer: PHP/' . phpversion() ]; if ($isHTML) { $headers[] = 'Content-Type: text/html; charset=UTF-8'; $headers[] = 'MIME-Version: 1.0'; } return mail($to, $subject, $message, implode("\r\n", $headers)); } /** * Log system activity */ function logSystemActivity($activity, $details = '', $userId = null) { if (!$userId && isLoggedIn()) { $userId = $_SESSION['user_id']; } $data = [ 'user_id' => $userId, 'activity' => $activity, 'details' => $details, 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'created_at' => date('Y-m-d H:i:s') ]; try { insertData('activity_logs', $data); } catch (Exception $e) { error_log("Failed to log activity: " . $e->getMessage()); } } /** * Get application settings */ function getAppSettings() { static $settings = null; if ($settings === null) { $settingsData = fetchAll("SELECT setting_key, setting_value FROM settings"); $settings = []; foreach ($settingsData as $setting) { $settings[$setting['setting_key']] = $setting['setting_value']; } } return $settings; } /** * Update application setting */ function updateAppSetting($key, $value) { $existing = fetchRow("SELECT id FROM settings WHERE setting_key = ?", [$key]); if ($existing) { updateData('settings', ['setting_value' => $value], 'setting_key = ?', [$key]); } else { insertData('settings', [ 'setting_key' => $key, 'setting_value' => $value, 'description' => '' ]); } // Clear cached settings getAppSettings.clear(); } /** * Format number for Indian currency */ function formatIndianCurrency($number) { return '₹' . number_format($number, 2); } /** * Convert number to words (for invoices, etc.) */ function numberToWords($number) { $words = [ 0 => 'Zero', 1 => 'One', 2 => 'Two', 3 => 'Three', 4 => 'Four', 5 => 'Five', 6 => 'Six', 7 => 'Seven', 8 => 'Eight', 9 => 'Nine', 10 => 'Ten', 11 => 'Eleven', 12 => 'Twelve', 13 => 'Thirteen', 14 => 'Fourteen', 15 => 'Fifteen', 16 => 'Sixteen', 17 => 'Seventeen', 18 => 'Eighteen', 19 => 'Nineteen', 20 => 'Twenty', 30 => 'Thirty', 40 => 'Forty', 50 => 'Fifty', 60 => 'Sixty', 70 => 'Seventy', 80 => 'Eighty', 90 => 'Ninety' ]; if ($number < 21) { return $words[$number]; } elseif ($number < 100) { return $words[10 * floor($number / 10)] . ($number % 10 ? ' ' . $words[$number % 10] : ''); } elseif ($number < 1000) { return $words[floor($number / 100)] . ' Hundred' . ($number % 100 ? ' ' . numberToWords($number % 100) : ''); } elseif ($number < 100000) { return numberToWords(floor($number / 1000)) . ' Thousand' . ($number % 1000 ? ' ' . numberToWords($number % 1000) : ''); } elseif ($number < 10000000) { return numberToWords(floor($number / 100000)) . ' Lakh' . ($number % 100000 ? ' ' . numberToWords($number % 100000) : ''); } else { return numberToWords(floor($number / 10000000)) . ' Crore' . ($number % 10000000 ? ' ' . numberToWords($number % 10000000) : ''); } } /** * Generate invoice number */ function generateInvoiceNumber($prefix = 'KA') { $year = date('Y'); $month = date('m'); // Get last invoice number for this month $lastInvoice = fetchRow( "SELECT invoice_number FROM sales WHERE invoice_number LIKE ? ORDER BY invoice_number DESC LIMIT 1", [$prefix . $year . $month . '%'] ); $sequence = 1; if ($lastInvoice) { $lastSequence = (int) substr($lastInvoice['invoice_number'], -4); $sequence = $lastSequence + 1; } return $prefix . $year . $month . str_pad($sequence, 4, '0', STR_PAD_LEFT); } /** * Check if user has permission */ function hasPermission($permission, $userId = null) { if (!$userId && isLoggedIn()) { $userId = $_SESSION['user_id']; } if (!$userId) { return false; } $user = fetchRow("SELECT role FROM users WHERE id = ?", [$userId]); if (!$user) { return false; } // Define role permissions $permissions = [ 'admin' => ['*'], // Admin has all permissions 'manager' => ['view_dashboard', 'manage_sales', 'manage_expenses', 'view_reports', 'manage_categories'], 'staff' => ['view_dashboard', 'add_sales', 'add_expenses', 'view_own_data'] ]; $userPermissions = $permissions[$user['role']] ?? []; return in_array('*', $userPermissions) || in_array($permission, $userPermissions); } /** * Backup database */ function backupDatabase($filename = null) { if (!$filename) { $filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql'; } $backupDir = __DIR__ . '/../backups/'; if (!is_dir($backupDir)) { mkdir($backupDir, 0755, true); } $backupFile = $backupDir . $filename; $command = sprintf( 'mysqldump --user=%s --password=%s --host=%s %s > %s', DB_USER, DB_PASS, DB_HOST, DB_NAME, escapeshellarg($backupFile) ); exec($command, $output, $return); return $return === 0 ? $backupFile : false; } /** * Get file size in human readable format */ function humanFileSize($size, $precision = 2) { for ($i = 0; ($size / 1024) > 0.9; $i++, $size /= 1024) {} return round($size, $precision) . ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'][$i]; } /** * Clean old log files */ function cleanOldLogs($days = 30) { $cutoffDate = date('Y-m-d H:i:s', strtotime("-$days days")); try { executeQuery("DELETE FROM activity_logs WHERE created_at < ?", [$cutoffDate]); return true; } catch (Exception $e) { error_log("Failed to clean old logs: " . $e->getMessage()); return false; } } ?> -------------------- END OF FILE -------------------- FILE: includes/header.php TYPE: PHP SIZE: 15.49 KB ------------------------------------------------------------ <?php echo isset($pageTitle) ? $pageTitle . ' - ' . APP_NAME : APP_NAME; ?>
-------------------- END OF FILE -------------------- FILE: includes/navigation.php TYPE: PHP SIZE: 0 B ------------------------------------------------------------ -------------------- END OF FILE -------------------- FILE: pages/base.php TYPE: PHP SIZE: 36.05 KB ------------------------------------------------------------ 'Base Settings'] ]; $success = ''; $error = ''; $activeTab = $_GET['tab'] ?? 'categories'; // Handle form submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { if (isset($_POST['action'])) { switch ($_POST['action']) { case 'add_category': $name = sanitizeInput($_POST['name']); $type = sanitizeInput($_POST['type']); $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $description = sanitizeInput($_POST['description'] ?? ''); if (empty($name) || empty($type)) { throw new Exception('Name and type are required.'); } // Check if category already exists $existing = fetchRow( "SELECT id FROM categories WHERE name = ? AND type = ?", [$name, $type] ); if ($existing) { throw new Exception('Category with this name and type already exists.'); } $categoryData = [ 'name' => $name, 'type' => $type, 'segment_id' => $segmentId, 'description' => $description, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s') ]; insertData('categories', $categoryData); logSystemActivity('Add Category', "Added {$type} category: {$name}"); $success = 'Category added successfully!'; break; case 'update_category': $id = (int)$_POST['id']; $name = sanitizeInput($_POST['name']); $description = sanitizeInput($_POST['description'] ?? ''); $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $status = sanitizeInput($_POST['status']); if (empty($name)) { throw new Exception('Category name is required.'); } updateData('categories', [ 'name' => $name, 'description' => $description, 'segment_id' => $segmentId, 'status' => $status ], 'id = ?', [$id]); logSystemActivity('Update Category', "Updated category ID: {$id}"); $success = 'Category updated successfully!'; break; case 'add_segment': $name = sanitizeInput($_POST['name']); $description = sanitizeInput($_POST['description'] ?? ''); if (empty($name)) { throw new Exception('Segment name is required.'); } // Check if segment already exists $existing = fetchRow("SELECT id FROM business_segments WHERE name = ?", [$name]); if ($existing) { throw new Exception('Business segment already exists.'); } $segmentData = [ 'name' => $name, 'description' => $description, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s') ]; insertData('business_segments', $segmentData); logSystemActivity('Add Segment', "Added business segment: {$name}"); $success = 'Business segment added successfully!'; break; case 'update_segment': $id = (int)$_POST['id']; $name = sanitizeInput($_POST['name']); $description = sanitizeInput($_POST['description'] ?? ''); $status = sanitizeInput($_POST['status']); updateData('business_segments', [ 'name' => $name, 'description' => $description, 'status' => $status ], 'id = ?', [$id]); logSystemActivity('Update Segment', "Updated segment ID: {$id}"); $success = 'Business segment updated successfully!'; break; case 'update_settings': $settings = $_POST['settings'] ?? []; foreach ($settings as $key => $value) { $value = sanitizeInput($value); updateAppSetting($key, $value); } logSystemActivity('Update Settings', 'Updated application settings'); $success = 'Settings updated successfully!'; break; } } } catch (Exception $e) { $error = $e->getMessage(); } } // Get data for display $categories = fetchAll(" SELECT c.*, bs.name as segment_name FROM categories c LEFT JOIN business_segments bs ON c.segment_id = bs.id ORDER BY c.type, c.name "); $businessSegments = fetchAll("SELECT * FROM business_segments ORDER BY name"); $appSettings = getAppSettings(); // Group categories by type $categoriesByType = []; foreach ($categories as $category) { $categoriesByType[$category['type']][] = $category; } include '../includes/header.php'; ?>
Add New Category
Manage Categories
No Categories Found

Start by adding your first category.

Revenue Categories
Name Segment Description Status Actions
Expense Categories
Name Segment Description Status Actions
Add Business Segment
Manage Business Segments
No Business Segments Found

Create segments to organize your business activities.

Name Description Status Created Actions
System Settings
-------------------- END OF FILE -------------------- FILE: pages/expenses.php TYPE: PHP SIZE: 46.25 KB ------------------------------------------------------------ 'Expense Management'] ]; $success = ''; $error = ''; $currentUser = getCurrentUser(); // Handle form submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $action = $_POST['action'] ?? ''; switch ($action) { case 'add_expense': $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $categoryId = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $expenseDate = $_POST['expense_date']; $description = sanitizeInput($_POST['description'] ?? ''); $paymentMethod = sanitizeInput($_POST['payment_method'] ?? ''); $vendor = sanitizeInput($_POST['vendor'] ?? ''); // Validation if (empty($title) || $amount <= 0 || empty($expenseDate)) { throw new Exception('Title, amount, and expense date are required.'); } // Handle receipt image upload $receiptImage = null; if (isset($_FILES['receipt_image']) && $_FILES['receipt_image']['error'] === UPLOAD_ERR_OK) { try { $receiptImage = handleFileUpload('receipt_image', '../uploads/receipts/'); } catch (Exception $e) { // Log error but don't fail the transaction error_log("Receipt upload failed: " . $e->getMessage()); } } $expenseData = [ 'title' => $title, 'amount' => $amount, 'category_id' => $categoryId, 'segment_id' => $segmentId, 'expense_date' => $expenseDate, 'description' => $description, 'payment_method' => $paymentMethod, 'vendor' => $vendor, 'receipt_image' => $receiptImage, 'created_by' => $currentUser['id'], 'created_at' => date('Y-m-d H:i:s') ]; $expenseId = insertData('expenses', $expenseData); if ($expenseId) { logSystemActivity('Add Expense', "Added expense: {$title} - " . formatIndianCurrency($amount)); $success = 'Expense recorded successfully!'; } else { throw new Exception('Failed to record expense.'); } break; case 'update_expense': $expenseId = (int)$_POST['expense_id']; $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $categoryId = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $expenseDate = $_POST['expense_date']; $description = sanitizeInput($_POST['description'] ?? ''); $paymentMethod = sanitizeInput($_POST['payment_method'] ?? ''); $vendor = sanitizeInput($_POST['vendor'] ?? ''); // Validation if (empty($title) || $amount <= 0 || empty($expenseDate)) { throw new Exception('Title, amount, and expense date are required.'); } // Check if user can edit this expense $existingExpense = fetchRow("SELECT * FROM expenses WHERE id = ?", [$expenseId]); if (!$existingExpense) { throw new Exception('Expense not found.'); } if ($currentUser['role'] !== 'admin' && $existingExpense['created_by'] != $currentUser['id']) { throw new Exception('You can only edit your own expense records.'); } $updateData = [ 'title' => $title, 'amount' => $amount, 'category_id' => $categoryId, 'segment_id' => $segmentId, 'expense_date' => $expenseDate, 'description' => $description, 'payment_method' => $paymentMethod, 'vendor' => $vendor ]; updateData('expenses', $updateData, 'id = ?', [$expenseId]); logSystemActivity('Update Expense', "Updated expense ID: {$expenseId}"); $success = 'Expense updated successfully!'; break; case 'delete_expense': $expenseId = (int)$_POST['expense_id']; // Check if user can delete this expense $existingExpense = fetchRow("SELECT * FROM expenses WHERE id = ?", [$expenseId]); if (!$existingExpense) { throw new Exception('Expense not found.'); } if ($currentUser['role'] !== 'admin' && $existingExpense['created_by'] != $currentUser['id']) { throw new Exception('You can only delete your own expense records.'); } // Delete associated receipt image if ($existingExpense['receipt_image'] && file_exists('../' . $existingExpense['receipt_image'])) { unlink('../' . $existingExpense['receipt_image']); } executeQuery("DELETE FROM expenses WHERE id = ?", [$expenseId]); logSystemActivity('Delete Expense', "Deleted expense: {$existingExpense['title']}"); $success = 'Expense deleted successfully!'; break; } } catch (Exception $e) { $error = $e->getMessage(); } } // Get filter parameters $dateFrom = $_GET['date_from'] ?? date('Y-m-01'); // First day of current month $dateTo = $_GET['date_to'] ?? date('Y-m-d'); // Today $segmentFilter = $_GET['segment'] ?? ''; $categoryFilter = $_GET['category'] ?? ''; $search = $_GET['search'] ?? ''; // Build query for expenses list $whereConditions = []; $params = []; // Date range filter if ($dateFrom) { $whereConditions[] = "e.expense_date >= ?"; $params[] = $dateFrom; } if ($dateTo) { $whereConditions[] = "e.expense_date <= ?"; $params[] = $dateTo; } // Segment filter if ($segmentFilter) { $whereConditions[] = "e.segment_id = ?"; $params[] = $segmentFilter; } // Category filter if ($categoryFilter) { $whereConditions[] = "e.category_id = ?"; $params[] = $categoryFilter; } // Search filter if ($search) { $whereConditions[] = "(e.title LIKE ? OR e.vendor LIKE ? OR e.description LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } // User-specific filter for non-admin users if ($currentUser['role'] !== 'admin' && !hasPermission('manage_expenses')) { $whereConditions[] = "e.created_by = ?"; $params[] = $currentUser['id']; } $whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : ''; // Get expenses with related data $expensesQuery = " SELECT e.*, bs.name as segment_name, c.name as category_name, u.full_name as created_by_name FROM expenses e LEFT JOIN business_segments bs ON e.segment_id = bs.id LEFT JOIN categories c ON e.category_id = c.id LEFT JOIN users u ON e.created_by = u.id {$whereClause} ORDER BY e.expense_date DESC, e.created_at DESC LIMIT 100 "; $expenses = fetchAll($expensesQuery, $params); // Calculate totals for the filtered data $totalQuery = " SELECT COUNT(*) as total_count, SUM(amount) as total_amount, AVG(amount) as avg_amount FROM expenses e {$whereClause} "; $totals = fetchRow($totalQuery, $params); // Get expense breakdown by category $categoryBreakdown = fetchAll(" SELECT c.name as category_name, SUM(e.amount) as total_amount, COUNT(e.id) as count FROM expenses e LEFT JOIN categories c ON e.category_id = c.id {$whereClause} GROUP BY e.category_id, c.name ORDER BY total_amount DESC LIMIT 10 ", $params); // Get data for dropdowns $businessSegments = getBusinessSegments(); $expenseCategories = getCategories('expense'); include '../includes/header.php'; ?>
Total Expenses
Transactions
Average Expense
Top Categories

No data available

0 ? ($category['total_amount'] / $totals['total_amount']) * 100 : 0; ?>
Clear
Expense Records
Showing of expenses
No Expenses Found

Try adjusting your filters or search criteria. Start by recording your first expense.

Description Vendor Category Amount Payment Date Actions
Not specified
-
-
-------------------- END OF FILE -------------------- FILE: pages/export_expenses.php TYPE: PHP SIZE: 3.49 KB ------------------------------------------------------------ = ?"; $params[] = $dateFrom; } if ($dateTo) { $whereConditions[] = "e.expense_date <= ?"; $params[] = $dateTo; } if ($segmentFilter) { $whereConditions[] = "e.segment_id = ?"; $params[] = $segmentFilter; } if ($categoryFilter) { $whereConditions[] = "e.category_id = ?"; $params[] = $categoryFilter; } if ($search) { $whereConditions[] = "(e.title LIKE ? OR e.vendor LIKE ? OR e.description LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } // User-specific filter for non-admin users if ($currentUser['role'] !== 'admin' && !hasPermission('manage_expenses')) { $whereConditions[] = "e.created_by = ?"; $params[] = $currentUser['id']; } $whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : ''; // Get expenses data for export $exportQuery = " SELECT e.title as 'Description', e.amount as 'Amount', e.expense_date as 'Expense Date', e.vendor as 'Vendor/Supplier', e.payment_method as 'Payment Method', bs.name as 'Business Segment', c.name as 'Category', e.description as 'Additional Notes', e.receipt_image as 'Receipt Attached', u.full_name as 'Created By', e.created_at as 'Created Date' FROM expenses e LEFT JOIN business_segments bs ON e.segment_id = bs.id LEFT JOIN categories c ON e.category_id = c.id LEFT JOIN users u ON e.created_by = u.id {$whereClause} ORDER BY e.expense_date DESC, e.created_at DESC "; $expensesData = fetchAll($exportQuery, $params); // Format data for CSV export $exportData = []; foreach ($expensesData as $row) { $formattedRow = []; foreach ($row as $key => $value) { // Format specific fields switch ($key) { case 'Amount': $formattedRow[$key] = number_format((float)$value, 2); break; case 'Expense Date': case 'Created Date': $formattedRow[$key] = $value ? date('d-m-Y H:i', strtotime($value)) : ''; break; case 'Receipt Attached': $formattedRow[$key] = $value ? 'Yes' : 'No'; break; case 'Payment Method': $formattedRow[$key] = $value ? ucfirst(str_replace('_', ' ', $value)) : ''; break; default: $formattedRow[$key] = $value ?? ''; } } $exportData[] = $formattedRow; } // Generate filename $filename = 'expenses_export_' . date('Y-m-d_H-i-s') . '.csv'; // Log export activity logSystemActivity('Export Expenses', "Exported " . count($exportData) . " expense records"); // Export to CSV exportToCSV($exportData, $filename); ?> -------------------- END OF FILE -------------------- FILE: pages/export_sales.php TYPE: PHP SIZE: 3.39 KB ------------------------------------------------------------ = ?"; $params[] = $dateFrom; } if ($dateTo) { $whereConditions[] = "s.sale_date <= ?"; $params[] = $dateTo; } if ($segmentFilter) { $whereConditions[] = "s.segment_id = ?"; $params[] = $segmentFilter; } if ($categoryFilter) { $whereConditions[] = "s.category_id = ?"; $params[] = $categoryFilter; } if ($search) { $whereConditions[] = "(s.title LIKE ? OR s.customer_name LIKE ? OR s.invoice_number LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } // User-specific filter for non-admin users if ($currentUser['role'] !== 'admin' && !hasPermission('manage_sales')) { $whereConditions[] = "s.created_by = ?"; $params[] = $currentUser['id']; } $whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : ''; // Get sales data for export $exportQuery = " SELECT s.invoice_number as 'Invoice Number', s.title as 'Product/Service', s.amount as 'Amount', s.quantity as 'Quantity', s.unit_price as 'Unit Price', s.sale_date as 'Sale Date', s.customer_name as 'Customer Name', s.customer_phone as 'Customer Phone', bs.name as 'Business Segment', c.name as 'Category', s.description as 'Description', u.full_name as 'Created By', s.created_at as 'Created Date' FROM sales s LEFT JOIN business_segments bs ON s.segment_id = bs.id LEFT JOIN categories c ON s.category_id = c.id LEFT JOIN users u ON s.created_by = u.id {$whereClause} ORDER BY s.sale_date DESC, s.created_at DESC "; $salesData = fetchAll($exportQuery, $params); // Format data for CSV export $exportData = []; foreach ($salesData as $row) { $formattedRow = []; foreach ($row as $key => $value) { // Format specific fields switch ($key) { case 'Amount': case 'Unit Price': $formattedRow[$key] = number_format((float)$value, 2); break; case 'Quantity': $formattedRow[$key] = number_format((float)$value, 1); break; case 'Sale Date': case 'Created Date': $formattedRow[$key] = $value ? date('d-m-Y H:i', strtotime($value)) : ''; break; default: $formattedRow[$key] = $value ?? ''; } } $exportData[] = $formattedRow; } // Generate filename $filename = 'sales_export_' . date('Y-m-d_H-i-s') . '.csv'; // Log export activity logSystemActivity('Export Sales', "Exported " . count($exportData) . " sales records"); // Export to CSV exportToCSV($exportData, $filename); ?> -------------------- END OF FILE -------------------- FILE: pages/investments.php TYPE: PHP SIZE: 48.13 KB ------------------------------------------------------------ 'Investments & Loans'] ]; $success = ''; $error = ''; $currentUser = getCurrentUser(); // Handle form submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $action = $_POST['action'] ?? ''; switch ($action) { case 'add_investment': $type = sanitizeInput($_POST['type']); $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $interestRate = (float)($_POST['interest_rate'] ?? 0); $startDate = $_POST['start_date']; $endDate = $_POST['end_date'] ?? null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $description = sanitizeInput($_POST['description'] ?? ''); $lender = sanitizeInput($_POST['lender'] ?? ''); $tenure = (int)($_POST['tenure'] ?? 0); // Validation if (empty($type) || empty($title) || $amount <= 0 || empty($startDate)) { throw new Exception('Type, title, amount, and start date are required.'); } if (!in_array($type, ['investment', 'loan'])) { throw new Exception('Invalid investment type.'); } if ($interestRate < 0 || $interestRate > 100) { throw new Exception('Interest rate must be between 0 and 100.'); } // Calculate EMI for loans $emi = 0; if ($type === 'loan' && $tenure > 0 && $interestRate > 0) { $emi = calculateEMI($amount, $interestRate, $tenure); } $investmentData = [ 'type' => $type, 'title' => $title, 'amount' => $amount, 'interest_rate' => $interestRate, 'start_date' => $startDate, 'end_date' => $endDate, 'segment_id' => $segmentId, 'description' => $description, 'lender' => $lender, 'tenure_months' => $tenure, 'emi_amount' => $emi, 'status' => 'active', 'created_by' => $currentUser['id'], 'created_at' => date('Y-m-d H:i:s') ]; $investmentId = insertData('investments', $investmentData); if ($investmentId) { logSystemActivity('Add ' . ucfirst($type), "Added {$type}: {$title} - " . formatIndianCurrency($amount)); $success = ucfirst($type) . ' recorded successfully!'; } else { throw new Exception('Failed to record ' . $type . '.'); } break; case 'update_investment': $investmentId = (int)$_POST['investment_id']; $type = sanitizeInput($_POST['type']); $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $interestRate = (float)($_POST['interest_rate'] ?? 0); $startDate = $_POST['start_date']; $endDate = $_POST['end_date'] ?? null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $description = sanitizeInput($_POST['description'] ?? ''); $lender = sanitizeInput($_POST['lender'] ?? ''); $tenure = (int)($_POST['tenure'] ?? 0); $status = sanitizeInput($_POST['status']); // Validation if (empty($title) || $amount <= 0 || empty($startDate)) { throw new Exception('Title, amount, and start date are required.'); } if (!in_array($status, ['active', 'completed', 'cancelled'])) { throw new Exception('Invalid status.'); } // Check if user can edit this investment $existingInvestment = fetchRow("SELECT * FROM investments WHERE id = ?", [$investmentId]); if (!$existingInvestment) { throw new Exception('Investment/Loan not found.'); } if ($currentUser['role'] !== 'admin' && $existingInvestment['created_by'] != $currentUser['id']) { throw new Exception('You can only edit your own records.'); } // Recalculate EMI if loan parameters changed $emi = $existingInvestment['emi_amount']; if ($type === 'loan' && $tenure > 0 && $interestRate > 0) { if ($amount != $existingInvestment['amount'] || $interestRate != $existingInvestment['interest_rate'] || $tenure != $existingInvestment['tenure_months']) { $emi = calculateEMI($amount, $interestRate, $tenure); } } $updateData = [ 'title' => $title, 'amount' => $amount, 'interest_rate' => $interestRate, 'start_date' => $startDate, 'end_date' => $endDate, 'segment_id' => $segmentId, 'description' => $description, 'lender' => $lender, 'tenure_months' => $tenure, 'emi_amount' => $emi, 'status' => $status ]; updateData('investments', $updateData, 'id = ?', [$investmentId]); logSystemActivity('Update ' . ucfirst($type), "Updated {$type} ID: {$investmentId}"); $success = ucfirst($type) . ' updated successfully!'; break; case 'delete_investment': $investmentId = (int)$_POST['investment_id']; // Check if user can delete this investment $existingInvestment = fetchRow("SELECT * FROM investments WHERE id = ?", [$investmentId]); if (!$existingInvestment) { throw new Exception('Investment/Loan not found.'); } if ($currentUser['role'] !== 'admin' && $existingInvestment['created_by'] != $currentUser['id']) { throw new Exception('You can only delete your own records.'); } executeQuery("DELETE FROM investments WHERE id = ?", [$investmentId]); logSystemActivity('Delete ' . ucfirst($existingInvestment['type']), "Deleted {$existingInvestment['type']}: {$existingInvestment['title']}"); $success = ucfirst($existingInvestment['type']) . ' deleted successfully!'; break; } } catch (Exception $e) { $error = $e->getMessage(); } } // Get filter parameters $typeFilter = $_GET['type'] ?? ''; $statusFilter = $_GET['status'] ?? ''; $segmentFilter = $_GET['segment'] ?? ''; $search = $_GET['search'] ?? ''; // Build query for investments list $whereConditions = []; $params = []; // Type filter if ($typeFilter) { $whereConditions[] = "i.type = ?"; $params[] = $typeFilter; } // Status filter if ($statusFilter) { $whereConditions[] = "i.status = ?"; $params[] = $statusFilter; } // Segment filter if ($segmentFilter) { $whereConditions[] = "i.segment_id = ?"; $params[] = $segmentFilter; } // Search filter if ($search) { $whereConditions[] = "(i.title LIKE ? OR i.lender LIKE ? OR i.description LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } // User-specific filter for non-admin users if ($currentUser['role'] !== 'admin' && !hasPermission('manage_investments')) { $whereConditions[] = "i.created_by = ?"; $params[] = $currentUser['id']; } $whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : ''; // Get investments with related data $investmentsQuery = " SELECT i.*, bs.name as segment_name, u.full_name as created_by_name FROM investments i LEFT JOIN business_segments bs ON i.segment_id = bs.id LEFT JOIN users u ON i.created_by = u.id {$whereClause} ORDER BY i.start_date DESC, i.created_at DESC "; $investments = fetchAll($investmentsQuery, $params); // Calculate totals and statistics $totalInvestments = 0; $totalLoans = 0; $totalEMI = 0; $activeCount = 0; foreach ($investments as $investment) { if ($investment['type'] === 'investment') { $totalInvestments += $investment['amount']; } else { $totalLoans += $investment['amount']; if ($investment['status'] === 'active') { $totalEMI += $investment['emi_amount']; } } if ($investment['status'] === 'active') { $activeCount++; } } // Get data for dropdowns $businessSegments = getBusinessSegments(); include '../includes/header.php'; ?>
Total Investments
Total Loans
Monthly EMI
Active Records
Clear
Investment & Loan Records
Showing records
No Records Found

Try adjusting your filters or search criteria. Start by adding your first investment or loan record.

Interest Rate: %
0): ?>
Monthly EMI:
Start Date:
End Date:
Lender/Bank:
Segment:

-------------------- END OF FILE -------------------- FILE: pages/sales.php TYPE: PHP SIZE: 46.81 KB ------------------------------------------------------------ 'Sales Management'] ]; $success = ''; $error = ''; $currentUser = getCurrentUser(); // Handle form submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $action = $_POST['action'] ?? ''; switch ($action) { case 'add_sale': $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $quantity = (float)($_POST['quantity'] ?? 1); $unitPrice = $quantity > 0 ? $amount / $quantity : $amount; $categoryId = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $saleDate = $_POST['sale_date']; $customerName = sanitizeInput($_POST['customer_name'] ?? ''); $customerPhone = sanitizeInput($_POST['customer_phone'] ?? ''); $description = sanitizeInput($_POST['description'] ?? ''); // Validation if (empty($title) || $amount <= 0 || empty($saleDate)) { throw new Exception('Title, amount, and sale date are required.'); } if ($customerPhone && !isValidPhone($customerPhone)) { throw new Exception('Please enter a valid customer phone number.'); } // Generate invoice number $invoiceNumber = generateInvoiceNumber(); // Handle receipt image upload $receiptImage = null; if (isset($_FILES['receipt_image']) && $_FILES['receipt_image']['error'] === UPLOAD_ERR_OK) { try { $receiptImage = handleFileUpload('receipt_image', '../uploads/receipts/'); } catch (Exception $e) { // Log error but don't fail the transaction error_log("Receipt upload failed: " . $e->getMessage()); } } $saleData = [ 'title' => $title, 'amount' => $amount, 'quantity' => $quantity, 'unit_price' => $unitPrice, 'category_id' => $categoryId, 'segment_id' => $segmentId, 'sale_date' => $saleDate, 'customer_name' => $customerName, 'customer_phone' => $customerPhone, 'description' => $description, 'invoice_number' => $invoiceNumber, 'receipt_image' => $receiptImage, 'created_by' => $currentUser['id'], 'created_at' => date('Y-m-d H:i:s') ]; $saleId = insertData('sales', $saleData); if ($saleId) { logSystemActivity('Add Sale', "Added sale: {$title} - " . formatIndianCurrency($amount)); $success = 'Sale recorded successfully! Invoice: ' . $invoiceNumber; } else { throw new Exception('Failed to record sale.'); } break; case 'update_sale': $saleId = (int)$_POST['sale_id']; $title = sanitizeInput($_POST['title']); $amount = (float)$_POST['amount']; $quantity = (float)($_POST['quantity'] ?? 1); $unitPrice = $quantity > 0 ? $amount / $quantity : $amount; $categoryId = !empty($_POST['category_id']) ? (int)$_POST['category_id'] : null; $segmentId = !empty($_POST['segment_id']) ? (int)$_POST['segment_id'] : null; $saleDate = $_POST['sale_date']; $customerName = sanitizeInput($_POST['customer_name'] ?? ''); $customerPhone = sanitizeInput($_POST['customer_phone'] ?? ''); $description = sanitizeInput($_POST['description'] ?? ''); // Validation if (empty($title) || $amount <= 0 || empty($saleDate)) { throw new Exception('Title, amount, and sale date are required.'); } if ($customerPhone && !isValidPhone($customerPhone)) { throw new Exception('Please enter a valid customer phone number.'); } // Check if user can edit this sale $existingSale = fetchRow("SELECT * FROM sales WHERE id = ?", [$saleId]); if (!$existingSale) { throw new Exception('Sale not found.'); } if ($currentUser['role'] !== 'admin' && $existingSale['created_by'] != $currentUser['id']) { throw new Exception('You can only edit your own sales records.'); } $updateData = [ 'title' => $title, 'amount' => $amount, 'quantity' => $quantity, 'unit_price' => $unitPrice, 'category_id' => $categoryId, 'segment_id' => $segmentId, 'sale_date' => $saleDate, 'customer_name' => $customerName, 'customer_phone' => $customerPhone, 'description' => $description ]; updateData('sales', $updateData, 'id = ?', [$saleId]); logSystemActivity('Update Sale', "Updated sale ID: {$saleId}"); $success = 'Sale updated successfully!'; break; case 'delete_sale': $saleId = (int)$_POST['sale_id']; // Check if user can delete this sale $existingSale = fetchRow("SELECT * FROM sales WHERE id = ?", [$saleId]); if (!$existingSale) { throw new Exception('Sale not found.'); } if ($currentUser['role'] !== 'admin' && $existingSale['created_by'] != $currentUser['id']) { throw new Exception('You can only delete your own sales records.'); } // Delete associated receipt image if ($existingSale['receipt_image'] && file_exists('../' . $existingSale['receipt_image'])) { unlink('../' . $existingSale['receipt_image']); } executeQuery("DELETE FROM sales WHERE id = ?", [$saleId]); logSystemActivity('Delete Sale', "Deleted sale: {$existingSale['title']}"); $success = 'Sale deleted successfully!'; break; } } catch (Exception $e) { $error = $e->getMessage(); } } // Get filter parameters $dateFrom = $_GET['date_from'] ?? date('Y-m-01'); // First day of current month $dateTo = $_GET['date_to'] ?? date('Y-m-d'); // Today $segmentFilter = $_GET['segment'] ?? ''; $categoryFilter = $_GET['category'] ?? ''; $search = $_GET['search'] ?? ''; // Build query for sales list $whereConditions = []; $params = []; // Date range filter if ($dateFrom) { $whereConditions[] = "s.sale_date >= ?"; $params[] = $dateFrom; } if ($dateTo) { $whereConditions[] = "s.sale_date <= ?"; $params[] = $dateTo; } // Segment filter if ($segmentFilter) { $whereConditions[] = "s.segment_id = ?"; $params[] = $segmentFilter; } // Category filter if ($categoryFilter) { $whereConditions[] = "s.category_id = ?"; $params[] = $categoryFilter; } // Search filter if ($search) { $whereConditions[] = "(s.title LIKE ? OR s.customer_name LIKE ? OR s.invoice_number LIKE ?)"; $params[] = "%$search%"; $params[] = "%$search%"; $params[] = "%$search%"; } // User-specific filter for non-admin users if ($currentUser['role'] !== 'admin' && !hasPermission('manage_sales')) { $whereConditions[] = "s.created_by = ?"; $params[] = $currentUser['id']; } $whereClause = !empty($whereConditions) ? 'WHERE ' . implode(' AND ', $whereConditions) : ''; // Get sales with related data $salesQuery = " SELECT s.*, bs.name as segment_name, c.name as category_name, u.full_name as created_by_name FROM sales s LEFT JOIN business_segments bs ON s.segment_id = bs.id LEFT JOIN categories c ON s.category_id = c.id LEFT JOIN users u ON s.created_by = u.id {$whereClause} ORDER BY s.sale_date DESC, s.created_at DESC LIMIT 100 "; $sales = fetchAll($salesQuery, $params); // Calculate totals for the filtered data $totalQuery = " SELECT COUNT(*) as total_count, SUM(amount) as total_amount, AVG(amount) as avg_amount, SUM(quantity) as total_quantity FROM sales s {$whereClause} "; $totals = fetchRow($totalQuery, $params); // Get data for dropdowns $businessSegments = getBusinessSegments(); $revenueCategories = getCategories('revenue'); include '../includes/header.php'; ?>
Total Sales
Transactions
Average Sale
Total Quantity
Clear
Sales Records
Showing of sales
No Sales Found

Try adjusting your filters or search criteria. Start by recording your first sale.

Invoice/Details Customer Category Amount Quantity Date Actions
Qty:
Walk-in Customer
-
1): ?> @
-------------------- END OF FILE -------------------- FILE: pages/users.php TYPE: PHP SIZE: 33.54 KB ------------------------------------------------------------ 'User Management'] ]; $success = ''; $error = ''; $action = $_GET['action'] ?? 'list'; // Handle form submissions if ($_SERVER['REQUEST_METHOD'] === 'POST') { try { $postAction = $_POST['action'] ?? ''; switch ($postAction) { case 'add_user': $username = sanitizeInput($_POST['username']); $email = sanitizeInput($_POST['email']); $fullName = sanitizeInput($_POST['full_name']); $phone = sanitizeInput($_POST['phone'] ?? ''); $role = sanitizeInput($_POST['role']); $password = $_POST['password'] ?? ''; // Validation if (empty($username) || empty($email) || empty($fullName) || empty($password) || empty($role)) { throw new Exception('All required fields must be filled.'); } if (!isValidEmail($email)) { throw new Exception('Please enter a valid email address.'); } if (strlen($password) < 8) { throw new Exception('Password must be at least 8 characters long.'); } if ($phone && !isValidPhone($phone)) { throw new Exception('Please enter a valid phone number.'); } // Check if username or email already exists $existingUser = fetchRow( "SELECT id FROM users WHERE username = ? OR email = ?", [$username, $email] ); if ($existingUser) { throw new Exception('Username or email already exists.'); } // Create user $userData = [ 'username' => $username, 'email' => $email, 'password' => hashPassword($password), 'full_name' => $fullName, 'phone' => $phone, 'role' => $role, 'status' => 'active', 'created_at' => date('Y-m-d H:i:s') ]; $userId = insertData('users', $userData); if ($userId) { logSystemActivity('Add User', "Created user: {$username} ({$role})", $currentUser['id']); $success = 'User created successfully!'; } else { throw new Exception('Failed to create user.'); } break; case 'update_user': $userId = (int)$_POST['user_id']; $username = sanitizeInput($_POST['username']); $email = sanitizeInput($_POST['email']); $fullName = sanitizeInput($_POST['full_name']); $phone = sanitizeInput($_POST['phone'] ?? ''); $role = sanitizeInput($_POST['role']); $status = sanitizeInput($_POST['status']); // Validation if (empty($username) || empty($email) || empty($fullName) || empty($role)) { throw new Exception('All required fields must be filled.'); } if (!isValidEmail($email)) { throw new Exception('Please enter a valid email address.'); } if ($phone && !isValidPhone($phone)) { throw new Exception('Please enter a valid phone number.'); } // Check if username or email already exists (excluding current user) $existingUser = fetchRow( "SELECT id FROM users WHERE (username = ? OR email = ?) AND id != ?", [$username, $email, $userId] ); if ($existingUser) { throw new Exception('Username or email already exists.'); } // Don't allow changing own status to inactive if ($userId == $currentUser['id'] && $status === 'inactive') { throw new Exception('You cannot deactivate your own account.'); } // Update user $updateData = [ 'username' => $username, 'email' => $email, 'full_name' => $fullName, 'phone' => $phone, 'role' => $role, 'status' => $status ]; updateData('users', $updateData, 'id = ?', [$userId]); logSystemActivity('Update User', "Updated user ID: {$userId}", $currentUser['id']); $success = 'User updated successfully!'; break; case 'reset_password': $userId = (int)$_POST['user_id']; $newPassword = generateSecurePassword(); updateData('users', [ 'password' => hashPassword($newPassword) ], 'id = ?', [$userId]); // Get user details for logging $user = fetchRow("SELECT username, email FROM users WHERE id = ?", [$userId]); logSystemActivity('Reset Password', "Password reset for user: {$user['username']}", $currentUser['id']); $success = "Password reset successfully! New password: {$newPassword} (Please share this securely with the user)"; break; case 'delete_user': $userId = (int)$_POST['user_id']; // Don't allow deleting own account if ($userId == $currentUser['id']) { throw new Exception('You cannot delete your own account.'); } // Check if user has associated data $salesCount = fetchRow("SELECT COUNT(*) as count FROM sales WHERE created_by = ?", [$userId])['count']; $expensesCount = fetchRow("SELECT COUNT(*) as count FROM expenses WHERE created_by = ?", [$userId])['count']; if ($salesCount > 0 || $expensesCount > 0) { throw new Exception('Cannot delete user with existing sales or expense records. Please deactivate instead.'); } // Get user details for logging $user = fetchRow("SELECT username FROM users WHERE id = ?", [$userId]); // Delete user executeQuery("DELETE FROM users WHERE id = ?", [$userId]); logSystemActivity('Delete User', "Deleted user: {$user['username']}", $currentUser['id']); $success = 'User deleted successfully!'; break; } } catch (Exception $e) { $error = $e->getMessage(); } } // Get users list with statistics $users = fetchAll(" SELECT u.*, (SELECT COUNT(*) FROM sales WHERE created_by = u.id) as sales_count, (SELECT COUNT(*) FROM expenses WHERE created_by = u.id) as expenses_count, (SELECT SUM(amount) FROM sales WHERE created_by = u.id) as total_sales, (SELECT SUM(amount) FROM expenses WHERE created_by = u.id) as total_expenses FROM users u ORDER BY u.created_at DESC "); include '../includes/header.php'; ?>
Total Users
$u['status'] === 'active')); ?>
Active Users
$u['role'] === 'admin')); ?>
Administrators
$u['role'] === 'manager')); ?>
Managers
System Users
No Users Found

Start by adding your first user.

User Contact Role Status Activity Joined Actions
sales
expenses
-------------------- END OF FILE -------------------- FILE: sql/database.sql TYPE: SQL SIZE: 5.82 KB ------------------------------------------------------------ -- Kayal Aqua Fish Business Management Database -- Database: u752449863_kastore -- Users table CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, full_name VARCHAR(100) NOT NULL, role ENUM('admin', 'manager', 'staff') DEFAULT 'staff', phone VARCHAR(15), status ENUM('active', 'inactive') DEFAULT 'active', remember_token VARCHAR(100) NULL, remember_expires DATETIME NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Business segments CREATE TABLE IF NOT EXISTS business_segments ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, description TEXT, status ENUM('active', 'inactive') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Categories for expenses and revenue CREATE TABLE IF NOT EXISTS categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, type ENUM('expense', 'revenue') NOT NULL, segment_id INT NULL, description TEXT, status ENUM('active', 'inactive') DEFAULT 'active', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_segment (segment_id), INDEX idx_type (type) ); -- Investments and Loans CREATE TABLE IF NOT EXISTS investments ( id INT AUTO_INCREMENT PRIMARY KEY, type ENUM('investment', 'loan') NOT NULL, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, interest_rate DECIMAL(5,2) DEFAULT 0, start_date DATE NOT NULL, end_date DATE NULL, segment_id INT NULL, description TEXT, lender VARCHAR(200) NULL, tenure_months INT DEFAULT 0, emi_amount DECIMAL(10,2) DEFAULT 0, status ENUM('active', 'completed', 'cancelled') DEFAULT 'active', created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_segment (segment_id), INDEX idx_created_by (created_by), INDEX idx_type (type), INDEX idx_status (status) ); -- Expenses CREATE TABLE IF NOT EXISTS expenses ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, category_id INT NULL, segment_id INT NULL, expense_date DATE NOT NULL, description TEXT, vendor VARCHAR(200) NULL, payment_method VARCHAR(50) NULL, receipt_image VARCHAR(255) NULL, created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_segment (segment_id), INDEX idx_created_by (created_by), INDEX idx_expense_date (expense_date) ); -- Sales/Revenue CREATE TABLE IF NOT EXISTS sales ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, amount DECIMAL(12,2) NOT NULL, quantity DECIMAL(10,2) DEFAULT 1, unit_price DECIMAL(10,2) NULL, category_id INT NULL, segment_id INT NULL, sale_date DATE NOT NULL, customer_name VARCHAR(100) NULL, customer_phone VARCHAR(15) NULL, description TEXT, invoice_number VARCHAR(50) NULL, receipt_image VARCHAR(255) NULL, created_by INT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_segment (segment_id), INDEX idx_created_by (created_by), INDEX idx_sale_date (sale_date), INDEX idx_invoice (invoice_number) ); -- Settings table CREATE TABLE IF NOT EXISTS settings ( id INT AUTO_INCREMENT PRIMARY KEY, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Activity logs table CREATE TABLE IF NOT EXISTS activity_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NULL, action VARCHAR(100) NOT NULL, description TEXT, ip_address VARCHAR(45) NULL, user_agent TEXT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_user (user_id), INDEX idx_created_at (created_at) ); -- Insert default business segments (only if they don't exist) INSERT IGNORE INTO business_segments (id, name, description) VALUES (1, 'Fish Sales', 'Fresh fish retail and wholesale'), (2, 'Dry Fish Sales', 'Processed dry fish products'), (3, 'Ornamental Fish Sales', 'Decorative fish for aquariums'), (4, 'Fish Fry/Roast Shop', 'Cooked fish products'), (5, 'Fish Cutting Service', 'Fish processing and cutting service'); -- Insert default categories (only if they don't exist) INSERT IGNORE INTO categories (id, name, type, segment_id, description) VALUES -- Revenue categories (1, 'Fresh Fish Sales', 'revenue', 1, 'Revenue from fresh fish sales'), (2, 'Dry Fish Sales', 'revenue', 2, 'Revenue from dry fish products'), (3, 'Ornamental Fish Sales', 'revenue', 3, 'Revenue from ornamental fish'), (4, 'Fried Fish Sales', 'revenue', 4, 'Revenue from fried fish products'), (5, 'Cutting Service', 'revenue', 5, 'Revenue from fish cutting service'), -- Expense categories (6, 'Fish Purchase', 'expense', 1, 'Cost of purchasing fish'), (7, 'Transportation', 'expense', NULL, 'Transportation costs'), (8, 'Utilities', 'expense', NULL, 'Electricity, water, etc.'), (9, 'Staff Salaries', 'expense', NULL, 'Employee salaries'), (10, 'Equipment', 'expense', NULL, 'Equipment and tools'), (11, 'Maintenance', 'expense', NULL, 'Maintenance and repairs'), (12, 'Marketing', 'expense', NULL, 'Marketing and advertising'), (13, 'Rent', 'expense', NULL, 'Shop/facility rent'), (14, 'Other', 'expense', NULL, 'Other miscellaneous expenses'); -- Insert default settings (only if they don't exist) INSERT IGNORE INTO settings (setting_key, setting_value, description) VALUES ('company_name', 'Kayal Aqua', 'Company name'), ('currency', '₹', 'Default currency symbol'), ('date_format', 'Y-m-d', 'Default date format'), ('timezone', 'Asia/Kolkata', 'Default timezone'); -------------------- END OF FILE --------------------