============================================= AQIL001 PROJECT EXPORT ============================================= ############################################################ # FILE : .htaccess ############################################################ # php -- BEGIN cPanel-generated handler, do not edit # Set the “ea-php82” package as the default “PHP” programming language. AddHandler application/x-httpd-ea-php82 .php .php8 .phtml # php -- END cPanel-generated handler, do not edit ############################################################ # FILE : admin/backup-download.php ############################################################ query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN); $sql="SET FOREIGN_KEY_CHECKS=0;\n\n"; foreach($tables as $table){ $sql.="DROP TABLE IF EXISTS `{$table}`;\n"; $create=$pdo->query("SHOW CREATE TABLE `{$table}`")->fetch(PDO::FETCH_ASSOC); $sql.=$create['Create Table'].";\n\n"; $rows=$pdo->query("SELECT * FROM `{$table}`"); while($row=$rows->fetch(PDO::FETCH_ASSOC)){ $values=[]; foreach($row as $value){ if(is_null($value)){ $values[]="NULL"; }else{ $values[]=$pdo->quote($value); } } $sql.="INSERT INTO `{$table}` VALUES (".implode(',',$values).");\n"; } $sql.="\n\n"; } $sql.="SET FOREIGN_KEY_CHECKS=1;\n"; addLog( 'Backup', 'DOWNLOAD', 'Database backup downloaded.' ); $fileName='backup_'.date('Ymd_His').'.sql'; header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'.$fileName.'"'); header('Content-Length: '.strlen($sql)); echo $sql; exit; ############################################################ # FILE : admin/backup.php ############################################################ query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN); ?>

System Backup

This page is used to create a full database backup.

Database Tables
# Table Name
prepare(" SELECT password FROM users WHERE id=? LIMIT 1 "); $stmt->execute([ currentUserId() ]); $user=$stmt->fetch(); if(!password_verify($currentPassword,$user['password'])){ error('Current password is incorrect.'); redirect('change-password.php'); } if($newPassword!=$confirmPassword){ error('Passwords do not match.'); redirect('change-password.php'); } $password=password_hash( $newPassword, PASSWORD_BCRYPT, [ 'cost'=>PASSWORD_COST ] ); $stmt=$pdo->prepare(" UPDATE users SET password=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $password, currentUserId() ]); addLog( 'Profile', 'PASSWORD', 'Password changed.' ); success('Password changed successfully.'); redirect('profile.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Change Password


query(" SELECT COUNT(*) FROM orders ")->fetchColumn(); $newOrders=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='NEW' ")->fetchColumn(); $progressOrders=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='IN_PROGRESS' ")->fetchColumn(); $reviewOrders=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='REVIEW' ")->fetchColumn(); $completedOrders=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='COMPLETED' ")->fetchColumn(); $totalCustomers=$pdo->query(" SELECT COUNT(*) FROM users WHERE role_id=".ROLE_CUSTOMER." ")->fetchColumn(); $totalWorkers=$pdo->query(" SELECT COUNT(*) FROM users WHERE role_id=".ROLE_WORKER." ")->fetchColumn(); $totalIncome=$pdo->query(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE status='PAID' ")->fetchColumn(); $recentOrders=$pdo->query(" SELECT order_no, title, status, priority, deadline FROM orders ORDER BY id DESC LIMIT 10 "); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Total Orders

Completed

In Progress

Review


New Orders

Customers

Workers

Total Income


Recent Orders
fetch()): ?>
Order No Title Status Priority Deadline
prepare(" SELECT COUNT(*) FROM logs l LEFT JOIN users u ON u.id=l.user_id {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT l.*, CONCAT(u.first_name,' ',u.last_name) fullname FROM logs l LEFT JOIN users u ON u.id=l.user_id {$where} ORDER BY l.id DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); $logs=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

System Logs

ID Module Action Description User IP Date
1): ?>
prepare(" SELECT COUNT(*) FROM notifications {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT n.*, CONCAT( u.first_name, ' ', u.last_name ) fullname FROM notifications n LEFT JOIN users u ON u.id=n.user_id {$where} ORDER BY n.id DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); $notifications=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Notifications

ID User Title Message Status Date
Read Unread
1): ?>
prepare(" SELECT id, customer_id, worker_id FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $orderId ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $stmt=$pdo->prepare(" SELECT id, first_name, last_name FROM users WHERE id=? AND role_id=? LIMIT 1 "); $stmt->execute([ $workerId, ROLE_WORKER ]); $worker=$stmt->fetch(); if(!$worker){ error('Worker not found.'); redirect('order-view.php?id='.$orderId); } $pdo->prepare(" UPDATE orders SET worker_id=?, status='ASSIGNED', updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $workerId, $orderId ]); addTimeline( $orderId, 'ORDER_ASSIGNED', 'Worker assigned.', $worker['first_name'].' '.$worker['last_name'], 'fa-solid fa-user-check', 'success' ); addLog( 'Orders', 'ASSIGN', 'Worker assigned.', $orderId ); sendNotification( $workerId, 'New Order', 'You have been assigned to a new order.', 'order-view.php?id='.$orderId, $orderId ); success('Worker assigned successfully.'); redirect('order-view.php?id='.$orderId); ############################################################ # FILE : admin/order-cancel.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $reason=trim($_POST['reason']); $pdo->prepare(" UPDATE orders SET status='CANCELLED', cancel_reason=?, cancelled_at=NOW(), updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $reason, $id ]); addTimeline( $id, 'ORDER_CANCELLED', 'Order cancelled.', $reason, 'fa-solid fa-ban', 'danger' ); addLog( 'Orders', 'CANCEL', 'Order cancelled.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Cancelled', 'Your order has been cancelled.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Cancelled', 'Assigned order has been cancelled.', 'order-view.php?id='.$id, $id ); } success('Order cancelled.'); redirect('order-view.php?id='.$id); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Cancel Order


Back
prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($order['status']=='COMPLETED'){ warning('Order already completed.'); redirect('order-view.php?id='.$id); } $pdo->prepare(" UPDATE orders SET status='COMPLETED', completed_at=NOW(), updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_COMPLETED', 'Order completed.', 'Project successfully completed.', 'fa-solid fa-circle-check', 'success' ); addLog( 'Orders', 'COMPLETE', 'Order completed.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Completed', 'Your order has been completed.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Completed', 'The assigned order has been completed.', 'order-view.php?id='.$id, $id ); } success('Order completed successfully.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-create.php ############################################################ prepare(" INSERT INTO orders ( order_no, customer_id, created_by, title, project_type, subject, description, priority, deadline ) VALUES ( ?,?,?,?,?,?,?,?,? ) "); $stmt->execute([ $orderNo, $_POST['customer_id'], currentUserId(), trim($_POST['title']), trim($_POST['project_type']), trim($_POST['subject']), trim($_POST['description']), $_POST['priority'], $_POST['deadline'] ]); $orderId=$pdo->lastInsertId(); addTimeline( $orderId, 'ORDER_CREATED', 'Order created.' ); addLog( 'Orders', 'CREATE', 'New order created.', $orderId ); success('Order created.'); redirect('order-view.php?id='.$orderId); } $customers=$pdo->query(" SELECT id, first_name, last_name FROM users WHERE role_id=".ROLE_CUSTOMER." AND status='ACTIVE' ORDER BY first_name ")->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

New Order


prepare(" SELECT id, customer_id, worker_id, deadline FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $deadline=$_POST['deadline']; $pdo->prepare(" UPDATE orders SET deadline=?, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $deadline, $id ]); addTimeline( $id, 'DEADLINE_CHANGED', 'Deadline changed.', 'New deadline : '.$deadline, 'fa-solid fa-calendar-days', 'info' ); addLog( 'Orders', 'DEADLINE', 'Deadline updated.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Deadline Updated', 'Deadline of your order has been updated.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Deadline Updated', 'Deadline of assigned order has been updated.', 'order-view.php?id='.$id, $id ); } success('Deadline updated.'); redirect('order-view.php?id='.$id); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Update Deadline


Back
prepare(" SELECT id FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" DELETE FROM orders WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_DELETE', 'Order deleted.', 'Order permanently deleted.', 'fa-solid fa-trash', 'danger' ); addLog( 'Orders', 'DELETE', 'Order permanently deleted.', $id ); success('Order deleted successfully.'); redirect('orders.php'); ############################################################ # FILE : admin/order-edit.php ############################################################ prepare(" SELECT * FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ redirect('orders.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $stmt=$pdo->prepare(" UPDATE orders SET worker_id=?, title=?, project_type=?, subject=?, description=?, priority=?, status=?, deadline=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $_POST['worker_id'] ?: null, trim($_POST['title']), trim($_POST['project_type']), trim($_POST['subject']), trim($_POST['description']), $_POST['priority'], $_POST['status'], $_POST['deadline'], $id ]); addTimeline( $id, 'ORDER_UPDATED', 'Order updated.' ); addLog( 'Orders', 'UPDATE', 'Order updated.', $id ); success('Order updated.'); redirect('order-view.php?id='.$id); } $workers=$pdo->query(" SELECT id, first_name, last_name FROM users WHERE role_id=".ROLE_WORKER." AND status='ACTIVE' ORDER BY first_name ")->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Edit Order



prepare(" SELECT id, customer_id, worker_id, notes FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $notes=trim($_POST['notes']); $pdo->prepare(" UPDATE orders SET notes=?, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $notes, $id ]); addTimeline( $id, 'ORDER_NOTE', 'Admin note updated.', $notes, 'fa-solid fa-note-sticky', 'secondary' ); addLog( 'Orders', 'NOTE', 'Admin note updated.', $id ); if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Updated', 'Administrator updated the order notes.', 'order-view.php?id='.$id, $id ); } success('Notes saved.'); redirect('order-view.php?id='.$id); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Admin Notes


Back
prepare(" SELECT id, customer_id, worker_id, total_price, paid_amount, remaining_amount FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $total=(float)$_POST['total_price']; $paid=(float)$_POST['paid_amount']; $remaining=$total-$paid; if($remaining<0){ $remaining=0; } $pdo->prepare(" UPDATE orders SET total_price=?, paid_amount=?, remaining_amount=?, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $total, $paid, $remaining, $id ]); addTimeline( $id, 'ORDER_PRICE', 'Order price updated.', 'Total : '.formatMoney($total), 'fa-solid fa-money-bill-wave', 'success' ); addLog( 'Orders', 'PRICE', 'Order price updated.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Price', 'Price information has been updated.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Price', 'Price information has been updated.', 'order-view.php?id='.$id, $id ); } success('Price updated.'); redirect('order-view.php?id='.$id); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Update Price


Back
prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } if($order['status']!='COMPLETED'){ warning('Only completed orders can be reopened.'); redirect('order-view.php?id='.$id); } $pdo->prepare(" UPDATE orders SET status='IN_PROGRESS', completed_at=NULL, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_REOPENED', 'Order reopened.', 'Project returned to In Progress.', 'fa-solid fa-rotate-left', 'warning' ); addLog( 'Orders', 'REOPEN', 'Order reopened.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Reopened', 'Your order has been reopened.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Reopened', 'Assigned order has been reopened.', 'order-view.php?id='.$id, $id ); } success('Order reopened successfully.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-return.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" UPDATE orders SET status='IN_PROGRESS', updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_RETURNED', 'Order returned to worker.', 'Administrator returned the project for corrections.', 'fa-solid fa-rotate-left', 'warning' ); addLog( 'Orders', 'RETURN', 'Order returned to worker.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Returned', 'The project has been returned to the worker for corrections.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Revision Required', 'The administrator returned the project for corrections.', 'order-view.php?id='.$id, $id ); } success('Order returned successfully.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-review.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" UPDATE orders SET status='REVIEW', updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_REVIEW', 'Order sent to review.', 'Waiting for administrator review.', 'fa-solid fa-magnifying-glass', 'warning' ); addLog( 'Orders', 'REVIEW', 'Order moved to review.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Review', 'Your order is under review.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Review', 'Assigned order is under review.', 'order-view.php?id='.$id, $id ); } success('Order moved to review.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-revision.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" UPDATE orders SET status='IN_PROGRESS', updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_REVISION', 'Revision requested.', 'Administrator requested revision from the worker.', 'fa-solid fa-pen-to-square', 'warning' ); addLog( 'Orders', 'REVISION', 'Revision requested.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Revision', 'Your order has been sent for revision.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Revision Requested', 'Administrator requested a revision for this order.', 'order-view.php?id='.$id, $id ); } success('Revision request sent.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-start.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" UPDATE orders SET status='IN_PROGRESS', started_at=NOW(), updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $id, 'ORDER_STARTED', 'Order started.', 'Worker started the project.', 'fa-solid fa-play', 'primary' ); addLog( 'Orders', 'START', 'Order started.', $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Started', 'Work on your order has started.', 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Started', 'You started working on this order.', 'order-view.php?id='.$id, $id ); } success('Order started.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-status.php ############################################################ prepare(" SELECT id, customer_id, worker_id, status FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $pdo->prepare(" UPDATE orders SET status=?, updated_at=NOW(), completed_at= CASE WHEN ?='COMPLETED' THEN NOW() ELSE completed_at END WHERE id=? LIMIT 1 ") ->execute([ $status, $status, $id ]); addTimeline( $id, 'STATUS_CHANGED', 'Order status changed.', 'New Status : '.$status, 'fa-solid fa-flag', 'primary' ); addLog( 'Orders', 'STATUS', 'Status changed to '.$status, $id ); if($order['customer_id']){ sendNotification( $order['customer_id'], 'Order Status', 'Order status changed to '.$status, 'order-view.php?id='.$id, $id ); } if($order['worker_id']){ sendNotification( $order['worker_id'], 'Order Status', 'Order status changed to '.$status, 'order-view.php?id='.$id, $id ); } success('Status updated.'); redirect('order-view.php?id='.$id); ############################################################ # FILE : admin/order-unassign.php ############################################################ prepare(" SELECT id, worker_id FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $orderId ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } $workerId=$order['worker_id']; $pdo->prepare(" UPDATE orders SET worker_id=NULL, status='NEW', updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $orderId ]); addTimeline( $orderId, 'ORDER_UNASSIGNED', 'Worker removed.', 'Worker assignment cancelled.', 'fa-solid fa-user-minus', 'warning' ); addLog( 'Orders', 'UNASSIGN', 'Worker removed.', $orderId ); if($workerId){ sendNotification( $workerId, 'Order Removed', 'This order has been removed from your assignments.', 'order-view.php?id='.$orderId, $orderId ); } success('Worker removed successfully.'); redirect('order-view.php?id='.$orderId); ############################################################ # FILE : admin/order-view.php ############################################################ prepare(" SELECT o.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM orders o LEFT JOIN users c ON c.id=o.customer_id LEFT JOIN users w ON w.id=o.worker_id WHERE o.id=? LIMIT 1 "); $stmt->execute([ $id ]); $order=$stmt->fetch(); if(!$order){ redirect('orders.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Order No
Customer
Worker
Status
Priority
Deadline
Description
prepare($sql); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $sql=" SELECT o.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM orders o LEFT JOIN users c ON c.id=o.customer_id LEFT JOIN users w ON w.id=o.worker_id {$where} ORDER BY o.id DESC LIMIT {$offset},{$limit} "; $stmt=$pdo->prepare($sql); $stmt->execute($params); $orders=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>
# Order No Title Customer Worker Status Deadline Action
Open
prepare(" INSERT INTO permissions ( name, description, created_at ) VALUES ( ?,?,NOW() ) "); $stmt->execute([ trim($_POST['name']), trim($_POST['description']) ]); addLog( 'Permissions', 'CREATE', 'Permission created.' ); success('Permission created successfully.'); redirect('permissions.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Create Permission


Back
prepare(" SELECT * FROM permissions WHERE id=? LIMIT 1 "); $stmt->execute([$id]); $permission=$stmt->fetch(); if(!$permission){ error('Permission not found.'); redirect('permissions.php'); } $stmt=$pdo->prepare(" SELECT COUNT(*) FROM role_permissions WHERE permission_id=? "); $stmt->execute([$id]); if($stmt->fetchColumn()>0){ error('This permission is assigned to one or more roles.'); redirect('permissions.php'); } $pdo->prepare(" DELETE FROM permissions WHERE id=? LIMIT 1 ") ->execute([$id]); addLog( 'Permissions', 'DELETE', 'Permission deleted.' ); success('Permission deleted successfully.'); redirect('permissions.php'); ############################################################ # FILE : admin/permission-edit.php ############################################################ prepare(" SELECT * FROM permissions WHERE id=? LIMIT 1 "); $stmt->execute([$id]); $permission=$stmt->fetch(); if(!$permission){ error('Permission not found.'); redirect('permissions.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $stmt=$pdo->prepare(" UPDATE permissions SET name=?, description=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ trim($_POST['name']), trim($_POST['description']), $id ]); addLog( 'Permissions', 'UPDATE', 'Permission updated.' ); success('Permission updated successfully.'); redirect('permissions.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Edit Permission


Back
prepare(" SELECT COUNT(*) FROM permissions {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT * FROM permissions {$where} ORDER BY name ASC LIMIT {$offset},{$limit} "); $stmt->execute($params); $permissions=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Permissions

New Permission
ID Permission Description Action
Edit
1): ?>
prepare(" UPDATE users SET first_name=?, last_name=?, email=?, phone=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $firstName, $lastName, $email, $phone, currentUserId() ]); }else{ $password=password_hash( $_POST['password'], PASSWORD_BCRYPT, [ 'cost'=>PASSWORD_COST ] ); $stmt=$pdo->prepare(" UPDATE users SET first_name=?, last_name=?, email=?, phone=?, password=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $firstName, $lastName, $email, $phone, $password, currentUserId() ]); } addLog( 'Profile', 'UPDATE', 'Profile updated.' ); success('Profile updated successfully.'); redirect('profile.php'); } $stmt=$pdo->prepare(" SELECT * FROM users WHERE id=? LIMIT 1 "); $stmt->execute([ currentUserId() ]); $user=$stmt->fetch(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

My Profile




query(" SELECT u.id, u.first_name, u.last_name, u.email, COUNT(o.id) total_orders, SUM(CASE WHEN o.status='COMPLETED' THEN 1 ELSE 0 END) completed_orders, SUM(CASE WHEN o.status='IN_PROGRESS' THEN 1 ELSE 0 END) progress_orders, IFNULL(SUM(p.amount),0) total_payment FROM users u LEFT JOIN orders o ON o.customer_id=u.id LEFT JOIN payments p ON p.customer_id=u.id AND p.status='PAID' WHERE u.role_id=".ROLE_CUSTOMER." GROUP BY u.id ORDER BY total_orders DESC "); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Customers Report


fetch()): ?>
ID Customer Email Total Orders Completed In Progress Total Payment
query(" SELECT f.*, o.order_no, CONCAT(u.first_name,' ',u.last_name) uploader_name FROM order_files f LEFT JOIN orders o ON o.id=f.order_id LEFT JOIN users u ON u.id=f.uploaded_by WHERE f.is_deleted=0 ORDER BY f.created_at DESC "); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Files Report


fetch()): ?>
ID Order File Name Uploader Role Size Customer Worker Date
query(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE status='PAID' ")->fetchColumn(); $totalPending=$pdo->query(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE status='PENDING' ")->fetchColumn(); $totalCancelled=$pdo->query(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE status='CANCELLED' ")->fetchColumn(); $monthly=$pdo->query(" SELECT DATE_FORMAT(payment_date,'%Y-%m') month, SUM(amount) total FROM payments WHERE status='PAID' GROUP BY DATE_FORMAT(payment_date,'%Y-%m') ORDER BY month DESC LIMIT 12 "); ?>

Finance Report


Total Income

Pending Payments

Cancelled Payments


fetch()): ?>
Month Income
prepare(" SELECT o.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM orders o LEFT JOIN users c ON c.id=o.customer_id LEFT JOIN users w ON w.id=o.worker_id {$where} ORDER BY o.id DESC "); $stmt->execute($params); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Orders Report


fetch()): ?>
# Order No Title Customer Worker Status Priority Deadline
prepare(" SELECT p.*, o.order_no, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM payments p LEFT JOIN orders o ON o.id=p.order_id LEFT JOIN users c ON c.id=p.customer_id LEFT JOIN users w ON w.id=p.worker_id {$where} ORDER BY p.payment_date DESC,p.id DESC "); $stmt->execute($params); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Payments Report


fetch()): ?>
# Order Customer Worker Type Amount Status Date
prepare(" SELECT COUNT(*) FROM timeline t LEFT JOIN orders o ON o.id=t.order_id {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT t.*, o.order_no, CONCAT(u.first_name,' ',u.last_name) fullname FROM timeline t LEFT JOIN orders o ON o.id=t.order_id LEFT JOIN users u ON u.id=t.user_id {$where} ORDER BY t.created_at DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Timeline Report

ID Order Title Description User Date
1): ?>
query(" SELECT u.id, u.first_name, u.last_name, u.email, COUNT(o.id) total_orders, SUM(CASE WHEN o.status='COMPLETED' THEN 1 ELSE 0 END) completed_orders, SUM(CASE WHEN o.status='IN_PROGRESS' THEN 1 ELSE 0 END) progress_orders, IFNULL(SUM(p.amount),0) total_income FROM users u LEFT JOIN orders o ON o.worker_id=u.id LEFT JOIN payments p ON p.worker_id=u.id AND p.status='PAID' WHERE u.role_id=".ROLE_WORKER." GROUP BY u.id ORDER BY total_orders DESC "); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Workers Report


fetch()): ?>
ID Worker Email Total Orders Completed In Progress Total Income
query("SELECT COUNT(*) FROM orders")->fetchColumn(); $totalCompleted=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='COMPLETED' ")->fetchColumn(); $totalProgress=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='IN_PROGRESS' ")->fetchColumn(); $totalCancelled=$pdo->query(" SELECT COUNT(*) FROM orders WHERE status='CANCELLED' ")->fetchColumn(); $totalCustomers=$pdo->query(" SELECT COUNT(*) FROM users WHERE role_id=".ROLE_CUSTOMER." ")->fetchColumn(); $totalWorkers=$pdo->query(" SELECT COUNT(*) FROM users WHERE role_id=".ROLE_WORKER." ")->fetchColumn(); $totalIncome=$pdo->query(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE status='PAID' ")->fetchColumn(); $totalFiles=$pdo->query(" SELECT COUNT(*) FROM order_files WHERE is_deleted=0 ")->fetchColumn(); ?>
Total Orders

Completed

In Progress

Cancelled


Customers

Workers

Total Files

Total Income

prepare(" INSERT INTO roles ( name, description, color, status, created_at ) VALUES ( ?,?,?,?,NOW() ) "); $stmt->execute([ trim($_POST['name']), trim($_POST['description']), trim($_POST['color']), $_POST['status'] ]); addLog( 'Roles', 'CREATE', 'Role created.' ); success('Role created successfully.'); redirect('roles.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Create Role


Back
prepare(" SELECT * FROM roles WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $role=$stmt->fetch(); if(!$role){ error('Role not found.'); redirect('roles.php'); } $stmt=$pdo->prepare(" SELECT COUNT(*) FROM users WHERE role_id=? "); $stmt->execute([ $id ]); if($stmt->fetchColumn()>0){ error('There are users assigned to this role.'); redirect('roles.php'); } $pdo->prepare(" DELETE FROM roles WHERE id=? LIMIT 1 ") ->execute([ $id ]); addLog( 'Roles', 'DELETE', 'Role deleted.' ); success('Role deleted successfully.'); redirect('roles.php'); ############################################################ # FILE : admin/role-edit.php ############################################################ prepare(" SELECT * FROM roles WHERE id=? LIMIT 1 "); $stmt->execute([$id]); $role=$stmt->fetch(); if(!$role){ error('Role not found.'); redirect('roles.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $stmt=$pdo->prepare(" UPDATE roles SET name=?, description=?, color=?, status=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ trim($_POST['name']), trim($_POST['description']), trim($_POST['color']), $_POST['status'], $id ]); addLog( 'Roles', 'UPDATE', 'Role updated.' ); success('Role updated successfully.'); redirect('roles.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Edit Role


Back
prepare(" SELECT * FROM roles WHERE id=? LIMIT 1 "); $stmt->execute([$roleId]); $role=$stmt->fetch(); if(!$role){ error('Role not found.'); redirect('roles.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); $pdo->prepare(" DELETE FROM role_permissions WHERE role_id=? ")->execute([$roleId]); if(!empty($_POST['permissions'])){ $insert=$pdo->prepare(" INSERT INTO role_permissions ( role_id, permission_id ) VALUES ( ?,? ) "); foreach($_POST['permissions'] as $permissionId){ $insert->execute([ $roleId, (int)$permissionId ]); } } addLog( 'Roles', 'PERMISSION', 'Role permissions updated.' ); success('Permissions updated successfully.'); redirect('roles.php'); } $permissions=$pdo->query(" SELECT * FROM permissions ORDER BY name ")->fetchAll(); $stmt=$pdo->prepare(" SELECT permission_id FROM role_permissions WHERE role_id=? "); $stmt->execute([$roleId]); $current=array_column( $stmt->fetchAll(), 'permission_id' ); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Role Permissions



Back
prepare(" SELECT COUNT(*) FROM roles {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT * FROM roles {$where} ORDER BY id ASC LIMIT {$offset},{$limit} "); $stmt->execute($params); $roles=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>
ID Role Name Description Color Status Action
Edit
1): ?>
$value){ if($key=='_token'){ continue; } $stmt=$pdo->prepare(" UPDATE settings SET setting_value=?, updated_at=NOW() WHERE setting_key=? "); $stmt->execute([ trim($value), $key ]); } addLog( 'Settings', 'MAIL', 'Mail settings updated.' ); success('Mail settings updated.'); redirect('settings-mail.php'); } $settings=[]; $stmt=$pdo->query(" SELECT setting_key, setting_value FROM settings "); while($row=$stmt->fetch()){ $settings[$row['setting_key']]=$row['setting_value']; } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Mail Settings


$value){ if($key=='_token'){ continue; } $stmt=$pdo->prepare(" UPDATE settings SET setting_value=?, updated_at=NOW() WHERE setting_key=? "); $stmt->execute([ trim($value), $key ]); } addLog( 'Settings', 'NOTIFICATION', 'Notification settings updated.' ); success('Notification settings updated.'); redirect('settings-notification.php'); } $settings=[]; $stmt=$pdo->query(" SELECT setting_key, setting_value FROM settings "); while($row=$stmt->fetch()){ $settings[$row['setting_key']]=$row['setting_value']; } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Notification Settings


$value){ if($key=='_token'){ continue; } $stmt=$pdo->prepare(" UPDATE settings SET setting_value=?, updated_at=NOW() WHERE setting_key=? "); $stmt->execute([ trim($value), $key ]); } addLog( 'Settings', 'SECURITY', 'Security settings updated.' ); success('Security settings updated.'); redirect('settings-security.php'); } $settings=[]; $stmt=$pdo->query(" SELECT setting_key, setting_value FROM settings "); while($row=$stmt->fetch()){ $settings[$row['setting_key']]=$row['setting_value']; } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Security Settings


$value){ if($key=='_token'){ continue; } $stmt=$pdo->prepare(" UPDATE settings SET setting_value=?, updated_at=NOW() WHERE setting_key=? "); $stmt->execute([ trim($value), $key ]); } addLog( 'Settings', 'UPDATE', 'System settings updated.' ); success('Settings updated successfully.'); redirect('settings.php'); } $settings=[]; $stmt=$pdo->query(" SELECT setting_key, setting_value FROM settings "); while($row=$stmt->fetch()){ $settings[$row['setting_key']]=$row['setting_value']; } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

System Settings


prepare(" INSERT INTO users ( role_id, first_name, last_name, username, email, phone, password, status, created_at ) VALUES ( ?,?,?,?,?,?,?,?,NOW() ) "); $stmt->execute([ $_POST['role_id'], trim($_POST['first_name']), trim($_POST['last_name']), trim($_POST['username']), trim($_POST['email']), trim($_POST['phone']), password_hash( $_POST['password'], PASSWORD_BCRYPT, [ 'cost'=>PASSWORD_COST ] ), $_POST['status'] ]); addLog( 'Users', 'CREATE', 'User created.' ); success('User created successfully.'); redirect('users.php'); } $roles=$pdo->query(" SELECT id, name FROM roles ORDER BY id ")->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Create User






Back
prepare(" SELECT * FROM users WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $user=$stmt->fetch(); if(!$user){ error('User not found.'); redirect('users.php'); } $pdo->prepare(" DELETE FROM users WHERE id=? LIMIT 1 ") ->execute([ $id ]); addLog( 'Users', 'DELETE', 'User deleted.' ); success('User deleted successfully.'); redirect('users.php'); ############################################################ # FILE : admin/user-edit.php ############################################################ prepare(" SELECT * FROM users WHERE id=? LIMIT 1 "); $stmt->execute([$id]); $user=$stmt->fetch(); if(!$user){ error('User not found.'); redirect('users.php'); } if($_SERVER['REQUEST_METHOD']=='POST'){ verifyCsrf(); if(trim($_POST['password'])!=''){ $password=password_hash( $_POST['password'], PASSWORD_BCRYPT, [ 'cost'=>PASSWORD_COST ] ); $stmt=$pdo->prepare(" UPDATE users SET role_id=?, first_name=?, last_name=?, username=?, email=?, phone=?, password=?, status=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $_POST['role_id'], trim($_POST['first_name']), trim($_POST['last_name']), trim($_POST['username']), trim($_POST['email']), trim($_POST['phone']), $password, $_POST['status'], $id ]); }else{ $stmt=$pdo->prepare(" UPDATE users SET role_id=?, first_name=?, last_name=?, username=?, email=?, phone=?, status=?, updated_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $_POST['role_id'], trim($_POST['first_name']), trim($_POST['last_name']), trim($_POST['username']), trim($_POST['email']), trim($_POST['phone']), $_POST['status'], $id ]); } addLog( 'Users', 'UPDATE', 'User updated.' ); success('User updated.'); redirect('users.php'); } $roles=$pdo->query(" SELECT id, name FROM roles ORDER BY id ")->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

Edit User






Back
prepare(" SELECT COUNT(*) FROM users u {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT u.*, r.name role_name FROM users u LEFT JOIN roles r ON r.id=u.role_id {$where} ORDER BY u.id DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); $users=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>
ID Full Name Username Email Role Status Created Action
Edit
1): ?>
prepare(" SELECT f.*, o.customer_id, o.worker_id FROM order_files f INNER JOIN orders o ON o.id=f.order_id WHERE f.id=? AND f.is_deleted=0 LIMIT 1 "); $stmt->execute([ $id ]); $file=$stmt->fetch(); if(!$file){ exit('File not found.'); } $allow=false; if( isSuperAdmin() || isAdmin() ){ $allow=true; } elseif( isCustomer() && $file['customer_id']==currentUserId() && $file['visible_customer'] ){ $allow=true; } elseif( isWorker() && $file['worker_id']==currentUserId() && $file['visible_worker'] ){ $allow=true; } if(!$allow){ exit('Permission denied.'); } $path= ORDER_UPLOAD_PATH. $file['folder']. '/'. $file['stored_name']; if(!file_exists($path)){ exit('Physical file not found.'); } $pdo->prepare(" UPDATE order_files SET download_count=download_count+1, last_download_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); header('Content-Description: File Transfer'); header('Content-Type: '.$file['mime_type']); header('Content-Length: '.filesize($path)); header('Content-Disposition: attachment; filename="'.basename($file['real_name']).'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); readfile($path); exit; ############################################################ # FILE : ajax/files/file-visible-customer.php ############################################################ prepare(" SELECT id, order_id, visible_customer FROM order_files WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $file=$stmt->fetch(); if(!$file){ exit('File not found.'); } $newValue=$file['visible_customer'] ? 0 : 1; $pdo->prepare(" UPDATE order_files SET visible_customer=?, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $newValue, $id ]); addTimeline( $file['order_id'], 'FILE_VISIBLE_CUSTOMER', $newValue ? 'File shared with Customer.' : 'File hidden from Customer.' ); addLog( 'Files', 'VISIBLE_CUSTOMER', $newValue ? 'Visible Customer ON' : 'Visible Customer OFF', $file['order_id'] ); header( 'Location: ../../admin/order-view.php?id='. $file['order_id']. '&tab=files' ); exit; ############################################################ # FILE : ajax/files/file-visible-worker.php ############################################################ prepare(" SELECT id, order_id, visible_worker FROM order_files WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $file=$stmt->fetch(); if(!$file){ exit('File not found.'); } $newValue=$file['visible_worker'] ? 0 : 1; $pdo->prepare(" UPDATE order_files SET visible_worker=?, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $newValue, $id ]); addTimeline( $file['order_id'], 'FILE_VISIBLE_WORKER', $newValue ? 'File shared with Worker.' : 'File hidden from Worker.' ); addLog( 'Files', 'VISIBLE_WORKER', $newValue ? 'Visible Worker ON' : 'Visible Worker OFF', $file['order_id'] ); header( 'Location: ../../admin/order-view.php?id='. $file['order_id']. '&tab=files' ); exit; ############################################################ # FILE : ajax/messages/delete-message.php ############################################################ prepare(" SELECT * FROM messages WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); $message=$stmt->fetch(); if(!$message){ exit('Message not found.'); } if( !isSuperAdmin() && !isAdmin() && $message['sender_id']!=currentUserId() ){ exit('Permission denied.'); } $pdo->prepare(" UPDATE messages SET deleted_by_sender=1, updated_at=NOW() WHERE id=? LIMIT 1 ") ->execute([ $id ]); addTimeline( $message['order_id'], 'MESSAGE_DELETE', 'Message deleted.' ); addLog( 'Messages', 'DELETE', 'Message deleted.', $message['order_id'] ); echo 'OK'; ############################################################ # FILE : ajax/messages/load-messages.php ############################################################ prepare(" SELECT m.*, u.first_name, u.last_name, r.name role_name, r.color role_color FROM messages m INNER JOIN users u ON u.id=m.sender_id INNER JOIN roles r ON r.id=u.role_id WHERE m.order_id=? ORDER BY m.created_at ASC "); $stmt->execute([ $orderId ]); while($row=$stmt->fetch()): ?>
()
prepare(" SELECT * FROM order_files WHERE message_id=? AND is_deleted=0 ORDER BY id "); $fileStmt->execute([ $row['id'] ]); while($file=$fileStmt->fetch()): ?>
############################################################ # FILE : ajax/messages/send-message.php ############################################################ prepare(" SELECT * FROM orders WHERE id=? LIMIT 1 "); $stmt->execute([ $orderId ]); $order=$stmt->fetch(); if(!$order){ exit('Order not found.'); } $receiverId=null; if(isCustomer()){ $receiverId=$order['worker_id']; } elseif(isWorker()){ $receiverId=$order['customer_id']; } $stmt=$pdo->prepare(" INSERT INTO messages ( order_id, sender_id, receiver_id, message, has_files, visible_customer, visible_worker, created_at ) VALUES ( ?,?,?,?,?,?,?,NOW() ) "); $stmt->execute([ $orderId, currentUserId(), $receiverId, $message, $hasFiles ? 1 : 0, isCustomer() ? 1 : 0, isWorker() ? 1 : 0 ]); $messageId=$pdo->lastInsertId(); if($hasFiles){ foreach($_FILES['chat_files']['name'] as $k=>$v){ if(empty($v)){ continue; } $file=[ 'name'=>$_FILES['chat_files']['name'][$k], 'type'=>$_FILES['chat_files']['type'][$k], 'tmp_name'=>$_FILES['chat_files']['tmp_name'][$k], 'error'=>$_FILES['chat_files']['error'][$k], 'size'=>$_FILES['chat_files']['size'][$k] ]; $upload=uploadFile( $file, $order['order_no'] ); if(!$upload){ continue; } $role='CUSTOMER'; if(isSuperAdmin()){ $role='SUPER_ADMIN'; } elseif(isAdmin()){ $role='ADMIN'; } elseif(isWorker()){ $role='WORKER'; } $stmt=$pdo->prepare(" INSERT INTO order_files ( order_id, message_id, uploaded_by, uploader_role, real_name, stored_name, extension, mime_type, file_size, folder, visible_customer, visible_worker, created_at ) VALUES ( ?,?,?,?,?,?,?,?,?,?,?,?,NOW() ) "); $stmt->execute([ $orderId, $messageId, currentUserId(), $role, $upload['real_name'], $upload['stored_name'], $upload['extension'], $upload['mime_type'], $upload['file_size'], $order['order_no'], isCustomer() ? 1 : 0, isWorker() ? 1 : 0 ]); } } addTimeline( $orderId, 'MESSAGE', 'New message' ); addLog( 'Messages', 'SEND', 'Message sent.', $orderId ); if($receiverId){ sendNotification( $receiverId, 'New Message', 'You have a new message.', 'order-view.php?id='.$orderId, $orderId, currentUserId() ); } echo 'OK'; ############################################################ # FILE : assets/css/mobile.css ############################################################ @media (max-width:992px){ .sidebar{ min-height:auto; } .chat-box{ height:450px; } .chat-message{ max-width:90%; } .order-tabs{ overflow-x:auto; flex-wrap:nowrap; padding-bottom:5px; } .order-tab{ white-space:nowrap; } .table{ font-size:13px; } .btn{ width:100%; margin-bottom:8px; } .form-control, .form-select{ font-size:16px; } } @media (max-width:768px){ .container-fluid{ padding:10px; } .card{ border-radius:8px; } .chat-box{ height:350px; padding:10px; } .chat-body{ font-size:14px; } .sidebar-item{ padding:14px; } .table{ display:block; overflow-x:auto; white-space:nowrap; } .navbar-brand{ font-size:16px; } } @media (max-width:576px){ .chat-message{ max-width:100%; } .order-tab{ padding:8px 15px; font-size:13px; } .card{ padding:10px; } h1,h2,h3{ font-size:18px; } } ############################################################ # FILE : assets/css/print.css ############################################################ @media print{ body{ background:#fff !important; color:#000 !important; font-size:12pt; } .sidebar, .navbar, .btn, .pagination, .order-tabs, .no-print{ display:none !important; } .col-lg-10, .col-md-9{ width:100% !important; max-width:100% !important; flex:0 0 100% !important; } .card{ border:none !important; box-shadow:none !important; margin-bottom:20px; } .table{ width:100%; border-collapse:collapse; } .table th, .table td{ border:1px solid #000; padding:8px; } a{ color:#000; text-decoration:none; } .chat-box{ height:auto !important; overflow:visible !important; } .chat-message{ max-width:100% !important; } .chat-body{ background:#fff !important; border:1px solid #ccc; } } ############################################################ # FILE : assets/css/style.css ############################################################ *{ margin:0; padding:0; box-sizing:border-box; } html, body{ height:100%; background:#f4f6f9; font-family:Segoe UI,Tahoma,sans-serif; font-size:14px; color:#333; } a{ text-decoration:none; } .navbar{ height:60px; } .card{ border:none; border-radius:10px; box-shadow:0 2px 8px rgba(0,0,0,.08); margin-bottom:20px; } .table thead{ background:#0d6efd; color:#fff; } .table th{ font-weight:600; vertical-align:middle; } .table td{ vertical-align:middle; } .form-control, .form-select{ border-radius:8px; } .pagination{ justify-content:center; } .badge{ border-radius:20px; padding:6px 12px; } .order-tabs{ display:flex; gap:10px; margin-bottom:20px; flex-wrap:wrap; } .order-tab{ padding:10px 20px; border:none; border-radius:8px; background:#dee2e6; cursor:pointer; } .order-tab.active{ background:#0d6efd; color:#fff; } .tab-content{ display:none; } .tab-content.active{ display:block; } .text-pending{ color:#ff9800; font-weight:600; } .text-approved{ color:#198754; font-weight:600; } .text-rejected{ color:#dc3545; font-weight:600; } /*================================================== DASHBOARD CARDS ==================================================*/ .dashboard-card{ background:#fff; border-radius:12px; padding:25px; box-shadow:0 5px 20px rgba(0,0,0,.08); transition:.25s; margin-bottom:20px; position:relative; overflow:hidden; } .dashboard-card:hover{ transform:translateY(-4px); box-shadow:0 12px 30px rgba(0,0,0,.12); } .dashboard-card h2{ font-size:34px; font-weight:700; margin-bottom:8px; } .dashboard-card p{ margin:0; color:#6b7280; font-size:15px; } .dashboard-card::after{ content:""; position:absolute; right:-35px; top:-35px; width:110px; height:110px; border-radius:50%; background:rgba(255,255,255,.12); } /*================================================== COLORS ==================================================*/ .bg-primary{ background:#2563eb!important; color:#fff; } .bg-success{ background:#16a34a!important; color:#fff; } .bg-danger{ background:#dc2626!important; color:#fff; } .bg-warning{ background:#f59e0b!important; color:#fff; } .bg-info{ background:#0ea5e9!important; color:#fff; } .bg-dark{ background:#1f2937!important; color:#fff; } .bg-secondary{ background:#64748b!important; color:#fff; } /*================================================== CARD HEADER ==================================================*/ .card-header{ background:#fff; font-weight:600; border-bottom:1px solid #ececec; padding:15px 20px; } /*================================================== CARD BODY ==================================================*/ .card-body{ padding:20px; } /*================================================== SIDEBAR ==================================================*/ .sidebar{ width:260px; min-height:100vh; background:#1e293b; position:fixed; left:0; top:0; z-index:1000; overflow-y:auto; box-shadow:4px 0 18px rgba(0,0,0,.12); } .sidebar::-webkit-scrollbar{ width:6px; } .sidebar::-webkit-scrollbar-thumb{ background:#475569; border-radius:20px; } .sidebar-logo{ height:70px; display:flex; align-items:center; justify-content:center; font-size:24px; font-weight:700; color:#fff; border-bottom:1px solid rgba(255,255,255,.08); } .sidebar-title{ padding:18px 22px 10px; color:#94a3b8; text-transform:uppercase; font-size:11px; letter-spacing:1px; font-weight:700; } .sidebar-item{ display:flex; align-items:center; gap:12px; padding:13px 22px; color:#cbd5e1; transition:.25s; border-left:4px solid transparent; } .sidebar-item i{ width:22px; text-align:center; font-size:16px; } .sidebar-item:hover{ background:#334155; color:#fff; border-left-color:#2563eb; } .sidebar-item.active{ background:#2563eb; color:#fff; border-left-color:#60a5fa; } .sidebar-item.active i{ color:#fff; } /*================================================== CONTENT ==================================================*/ .main-content{ margin-left:260px; padding:25px; min-height:100vh; } /*================================================== TOPBAR ==================================================*/ .topbar{ height:65px; background:#fff; border-radius:12px; display:flex; align-items:center; justify-content:space-between; padding:0 25px; margin-bottom:25px; box-shadow:0 4px 18px rgba(0,0,0,.06); } .topbar-left{ display:flex; align-items:center; gap:15px; } .topbar-title{ font-size:22px; font-weight:700; color:#1e293b; } .topbar-right{ display:flex; align-items:center; gap:20px; } .user-avatar{ width:42px; height:42px; border-radius:50%; background:#2563eb; color:#fff; display:flex; align-items:center; justify-content:center; font-weight:700; font-size:16px; } .user-name{ font-weight:600; color:#334155; } /*================================================== PREMIUM TABLES ==================================================*/ .table-responsive{ border-radius:12px; overflow:hidden; background:#fff; box-shadow:0 4px 18px rgba(0,0,0,.06); } .table{ width:100%; border-collapse:collapse; margin-bottom:0; } .table thead th{ background:#2563eb; color:#fff; font-weight:600; letter-spacing:.3px; border:none; padding:15px 18px; } .table tbody tr{ transition:.20s; } .table tbody tr:nth-child(even){ background:#fafbfc; } .table tbody tr:hover{ background:#edf4ff; } .table td{ padding:14px 18px; border-color:#eef2f7; } /*================================================== STATUS BADGES ==================================================*/ .status-new{ background:#3b82f6; color:#fff; } .status-assigned{ background:#8b5cf6; color:#fff; } .status-progress{ background:#f59e0b; color:#fff; } .status-review{ background:#0ea5e9; color:#fff; } .status-completed{ background:#22c55e; color:#fff; } .status-cancelled{ background:#ef4444; color:#fff; } .status-paid{ background:#16a34a; color:#fff; } .status-pending{ background:#f59e0b; color:#fff; } .status-unpaid{ background:#dc2626; color:#fff; } .badge{ display:inline-block; padding:7px 14px; border-radius:30px; font-size:12px; font-weight:600; letter-spacing:.3px; } /*================================================== HOVER EFFECT ==================================================*/ .table tbody tr{ cursor:pointer; } .table tbody tr:hover td{ transition:.20s; } /*================================================== EMPTY TABLE ==================================================*/ .table-empty{ text-align:center; color:#94a3b8; padding:40px; font-size:15px; } /*================================================== PREMIUM BUTTONS ==================================================*/ .btn{ display:inline-flex; align-items:center; justify-content:center; gap:8px; padding:10px 18px; border:none; border-radius:10px; font-size:14px; font-weight:600; cursor:pointer; transition:all .25s ease; text-decoration:none; white-space:nowrap; } .btn i{ font-size:14px; } .btn:hover{ transform:translateY(-2px); box-shadow:0 8px 18px rgba(0,0,0,.15); } .btn:active{ transform:scale(.98); } .btn:disabled{ opacity:.65; cursor:not-allowed; box-shadow:none; } /*================================================== BUTTON COLORS ==================================================*/ .btn-primary{ background:#2563eb; color:#fff; } .btn-primary:hover{ background:#1d4ed8; color:#fff; } .btn-success{ background:#16a34a; color:#fff; } .btn-success:hover{ background:#15803d; color:#fff; } .btn-danger{ background:#dc2626; color:#fff; } .btn-danger:hover{ background:#b91c1c; color:#fff; } .btn-warning{ background:#f59e0b; color:#fff; } .btn-warning:hover{ background:#d97706; color:#fff; } .btn-info{ background:#0ea5e9; color:#fff; } .btn-info:hover{ background:#0284c7; color:#fff; } .btn-dark{ background:#1f2937; color:#fff; } .btn-dark:hover{ background:#111827; color:#fff; } .btn-secondary{ background:#64748b; color:#fff; } .btn-secondary:hover{ background:#475569; color:#fff; } /*================================================== OUTLINE BUTTONS ==================================================*/ .btn-outline-primary{ background:#fff; color:#2563eb; border:2px solid #2563eb; } .btn-outline-primary:hover{ background:#2563eb; color:#fff; } .btn-outline-success{ background:#fff; color:#16a34a; border:2px solid #16a34a; } .btn-outline-success:hover{ background:#16a34a; color:#fff; } .btn-outline-danger{ background:#fff; color:#dc2626; border:2px solid #dc2626; } .btn-outline-danger:hover{ background:#dc2626; color:#fff; } /*================================================== BUTTON SIZES ==================================================*/ .btn-sm{ padding:6px 12px; font-size:12px; } .btn-lg{ padding:14px 28px; font-size:16px; } /*================================================== BUTTON GROUP ==================================================*/ .btn-group{ display:flex; gap:8px; flex-wrap:wrap; } /*================================================== ICON BUTTON ==================================================*/ .btn-icon{ width:40px; height:40px; padding:0; border-radius:50%; } .btn-icon i{ margin:0; } /*================================================== FLOAT ANIMATION ==================================================*/ .btn-float:hover{ animation:buttonFloat .35s ease; } @keyframes buttonFloat{ 0%{ transform:translateY(0); } 50%{ transform:translateY(-4px); } 100%{ transform:translateY(-2px); } } /*================================================== PREMIUM FORMS ==================================================*/ .form-label, label{ display:block; margin-bottom:8px; font-size:14px; font-weight:600; color:#374151; } .form-control, .form-select, .form-control-file, textarea, select, input{ width:100%; min-height:46px; padding:12px 15px; border:1px solid #dbe3ec; border-radius:10px; background:#fff; color:#374151; font-size:14px; transition:all .25s ease; outline:none; box-shadow:none; } textarea{ min-height:120px; resize:vertical; } .form-control:hover, .form-select:hover, textarea:hover, input:hover{ border-color:#93c5fd; } .form-control:focus, .form-select:focus, textarea:focus, input:focus{ border-color:#2563eb; box-shadow:0 0 0 4px rgba(37,99,235,.12); background:#fff; } .form-control::placeholder, textarea::placeholder{ color:#9ca3af; } .form-control:disabled, .form-select:disabled{ background:#f3f4f6; cursor:not-allowed; opacity:.8; } /*================================================== INPUT GROUP ==================================================*/ .input-group{ display:flex; align-items:stretch; width:100%; } .input-group-text{ display:flex; align-items:center; justify-content:center; padding:0 16px; background:#f8fafc; border:1px solid #dbe3ec; border-right:none; border-radius:10px 0 0 10px; color:#64748b; min-width:46px; } .input-group .form-control{ border-radius:0 10px 10px 0; } /*================================================== CHECKBOX & RADIO ==================================================*/ .form-check{ display:flex; align-items:center; gap:10px; margin-bottom:12px; } .form-check input{ width:18px; height:18px; cursor:pointer; } .form-check label{ margin:0; cursor:pointer; } /*================================================== SEARCH BOX ==================================================*/ .search-box{ width:100%; max-width:380px; margin-bottom:20px; } /*================================================== VALIDATION ==================================================*/ .is-valid{ border-color:#16a34a!important; } .is-invalid{ border-color:#dc2626!important; } .valid-feedback{ color:#16a34a; font-size:13px; margin-top:6px; } .invalid-feedback{ color:#dc2626; font-size:13px; margin-top:6px; } /*================================================== LOGIN CARD ==================================================*/ .login-wrapper{ min-height:100vh; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg,#2563eb,#1d4ed8); padding:30px; } .login-card{ width:100%; max-width:430px; background:#fff; border-radius:16px; padding:35px; box-shadow:0 20px 45px rgba(0,0,0,.18); } .login-card h2{ text-align:center; margin-bottom:10px; color:#1f2937; } .login-card p{ text-align:center; color:#6b7280; margin-bottom:30px; } .login-logo{ width:80px; height:80px; margin:0 auto 20px; display:flex; align-items:center; justify-content:center; border-radius:50%; background:#2563eb; color:#fff; font-size:30px; font-weight:bold; } /*================================================== FORM ROW ==================================================*/ .form-row{ display:flex; gap:20px; flex-wrap:wrap; } .form-row>.col, .form-row>div{ flex:1; min-width:240px; } /*================================================== MODAL ==================================================*/ .modal-content{ border:none; border-radius:14px; overflow:hidden; box-shadow:0 20px 45px rgba(0,0,0,.18); } .modal-header{ background:#2563eb; color:#fff; border:none; padding:18px 22px; } .modal-header h5{ margin:0; font-size:18px; font-weight:600; } .modal-header .btn-close{ filter:brightness(0) invert(1); } .modal-body{ padding:25px; } .modal-footer{ border-top:1px solid #edf2f7; padding:18px 22px; } /*================================================== ALERTS ==================================================*/ .alert{ border:none; border-radius:12px; padding:16px 20px; font-size:14px; margin-bottom:20px; display:flex; align-items:center; gap:12px; box-shadow:0 4px 14px rgba(0,0,0,.06); } .alert i{ font-size:18px; } .alert-success{ background:#ecfdf5; color:#166534; border-left:5px solid #22c55e; } .alert-danger{ background:#fef2f2; color:#991b1b; border-left:5px solid #ef4444; } .alert-warning{ background:#fffbeb; color:#92400e; border-left:5px solid #f59e0b; } .alert-info{ background:#eff6ff; color:#1d4ed8; border-left:5px solid #2563eb; } /*================================================== TOAST ==================================================*/ .toast{ border:none; border-radius:12px; overflow:hidden; box-shadow:0 12px 30px rgba(0,0,0,.15); } .toast-header{ background:#2563eb; color:#fff; border:none; } .toast-body{ padding:15px; } /*================================================== LOADING ==================================================*/ .loading{ display:flex; align-items:center; justify-content:center; padding:30px; } .spinner{ width:40px; height:40px; border:4px solid #dbeafe; border-top:4px solid #2563eb; border-radius:50%; animation:spin .8s linear infinite; } @keyframes spin{ 100%{ transform:rotate(360deg); } } /*================================================== PROGRESS ==================================================*/ .progress{ height:10px; border-radius:20px; overflow:hidden; background:#e5e7eb; } .progress-bar{ height:100%; background:#2563eb; transition:width .4s ease; } /*================================================== DROPDOWN ==================================================*/ .dropdown-menu{ border:none; border-radius:12px; overflow:hidden; box-shadow:0 12px 30px rgba(0,0,0,.12); padding:8px 0; } .dropdown-item{ padding:10px 18px; transition:.2s; } .dropdown-item:hover{ background:#eff6ff; color:#2563eb; } /*================================================== TOOLTIP ==================================================*/ .tooltip-inner{ background:#1e293b; border-radius:8px; padding:8px 12px; font-size:13px; } .tooltip.bs-tooltip-top .tooltip-arrow::before{ border-top-color:#1e293b; } /*================================================== BREADCRUMB ==================================================*/ .breadcrumb{ background:#fff; padding:12px 18px; border-radius:10px; margin-bottom:20px; box-shadow:0 3px 12px rgba(0,0,0,.05); } .breadcrumb-item a{ color:#2563eb; } .breadcrumb-item.active{ color:#6b7280; } /*================================================== TIMELINE ==================================================*/ .timeline{ position:relative; padding-left:35px; } .timeline::before{ content:""; position:absolute; left:11px; top:0; bottom:0; width:3px; background:#dbeafe; } .timeline-item{ position:relative; background:#fff; border-radius:12px; padding:18px 20px; margin-bottom:20px; box-shadow:0 4px 14px rgba(0,0,0,.06); border-left:none; } .timeline-item::before{ content:""; position:absolute; left:-30px; top:22px; width:16px; height:16px; border-radius:50%; background:#2563eb; border:4px solid #fff; box-shadow:0 0 0 3px #dbeafe; } .timeline-title{ font-size:15px; font-weight:700; color:#1e293b; margin-bottom:6px; } .timeline-text{ color:#64748b; font-size:14px; line-height:1.7; } .timeline-date{ margin-top:10px; color:#94a3b8; font-size:12px; } /*================================================== CHAT ==================================================*/ .chat-box{ background:#f8fafc; border-radius:12px; border:1px solid #e5e7eb; padding:20px; } .chat-message{ display:flex; margin-bottom:18px; } .chat-message.mine{ justify-content:flex-end; } .chat-message.other{ justify-content:flex-start; } .chat-body{ max-width:75%; padding:14px 18px; border-radius:14px; line-height:1.6; word-break:break-word; } .chat-message.mine .chat-body{ background:#2563eb; color:#fff; border-bottom-right-radius:4px; } .chat-message.other .chat-body{ background:#fff; color:#374151; border:1px solid #e5e7eb; border-bottom-left-radius:4px; } .chat-user{ font-weight:600; margin-bottom:5px; } .chat-time{ margin-top:8px; font-size:11px; opacity:.75; } /*================================================== MESSAGE INPUT ==================================================*/ .message-box{ display:flex; gap:12px; margin-top:20px; } .message-box textarea{ flex:1; min-height:52px; max-height:150px; } /*================================================== FILE UPLOAD ==================================================*/ .upload-box{ border:2px dashed #93c5fd; background:#f8fbff; border-radius:14px; padding:35px; text-align:center; transition:.25s; cursor:pointer; } .upload-box:hover{ border-color:#2563eb; background:#eff6ff; } .upload-box i{ font-size:42px; color:#2563eb; margin-bottom:12px; } .upload-box h5{ margin-bottom:8px; color:#1e293b; } .upload-box p{ margin:0; color:#64748b; } .upload-item{ display:flex; align-items:center; justify-content:space-between; gap:15px; padding:14px 18px; background:#fff; border:1px solid #e5e7eb; border-radius:12px; margin-top:12px; transition:.2s; } .upload-item:hover{ background:#f8fafc; } .upload-file{ display:flex; align-items:center; gap:12px; } .upload-file i{ font-size:24px; color:#2563eb; } .upload-name{ font-weight:600; color:#374151; } .upload-size{ font-size:12px; color:#94a3b8; } /*================================================== FILE ICON COLORS ==================================================*/ .file-pdf{ color:#dc2626; } .file-word{ color:#2563eb; } .file-excel{ color:#16a34a; } .file-zip{ color:#f59e0b; } .file-image{ color:#8b5cf6; } /*================================================== ORDER CARDS ==================================================*/ .order-card{ background:#fff; border-radius:14px; padding:22px; margin-bottom:20px; border:1px solid #e5e7eb; box-shadow:0 6px 18px rgba(0,0,0,.05); transition:all .25s ease; } .order-card:hover{ transform:translateY(-3px); box-shadow:0 12px 28px rgba(0,0,0,.10); } .order-header{ display:flex; justify-content:space-between; align-items:center; margin-bottom:18px; gap:15px; flex-wrap:wrap; } .order-title{ font-size:18px; font-weight:700; color:#1e293b; } .order-number{ font-size:13px; color:#64748b; } .order-body{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:18px; } .order-info{ display:flex; flex-direction:column; } .order-info small{ color:#94a3b8; margin-bottom:4px; } .order-info strong{ color:#1f2937; font-weight:600; } /*================================================== PAYMENT SUMMARY ==================================================*/ .payment-summary{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:20px; margin-bottom:25px; } .payment-card{ background:#fff; border-radius:14px; padding:24px; box-shadow:0 6px 18px rgba(0,0,0,.06); border:1px solid #eef2f7; transition:.25s; } .payment-card:hover{ transform:translateY(-4px); } .payment-card h6{ color:#64748b; margin-bottom:10px; font-size:14px; } .payment-card h2{ font-size:30px; color:#1e293b; margin:0; font-weight:700; } /*================================================== STATISTICS ==================================================*/ .stats-grid{ display:grid; grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:20px; } .stats-box{ background:#fff; border-radius:14px; padding:25px; text-align:center; border:1px solid #edf2f7; transition:.25s; } .stats-box:hover{ transform:translateY(-4px); box-shadow:0 10px 24px rgba(0,0,0,.08); } .stats-icon{ width:70px; height:70px; margin:auto; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:28px; margin-bottom:15px; background:#eff6ff; color:#2563eb; } .stats-number{ font-size:34px; font-weight:700; color:#1e293b; } .stats-title{ color:#64748b; margin-top:6px; } /*================================================== PROGRESS BLOCK ==================================================*/ .progress-box{ margin-bottom:18px; } .progress-title{ display:flex; justify-content:space-between; margin-bottom:8px; font-size:14px; font-weight:600; } .progress{ height:12px; border-radius:30px; overflow:hidden; background:#e5e7eb; } .progress-bar{ border-radius:30px; } /*================================================== QUICK ACTIONS ==================================================*/ .quick-actions{ display:flex; flex-wrap:wrap; gap:12px; } .quick-actions .btn{ min-width:170px; } /*================================================== INFO BOX ==================================================*/ .info-box{ display:flex; align-items:center; gap:15px; padding:18px; border-radius:14px; background:#fff; border:1px solid #e5e7eb; margin-bottom:20px; } .info-box-icon{ width:60px; height:60px; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:24px; background:#eff6ff; color:#2563eb; } .info-box-content h5{ margin-bottom:5px; color:#1f2937; } .info-box-content p{ margin:0; color:#64748b; } /*================================================== RESPONSIVE DESIGN ==================================================*/ /* ---------- Large Desktop ---------- */ @media (max-width:1400px){ .container{ max-width:1200px; } } /* ---------- Laptop ---------- */ @media (max-width:1200px){ .main-content{ margin-left:240px; padding:20px; } .sidebar{ width:240px; } .dashboard-card h2{ font-size:28px; } } /* ---------- Tablet ---------- */ @media (max-width:992px){ .sidebar{ width:220px; } .main-content{ margin-left:220px; padding:18px; } .dashboard-card{ padding:18px; } .topbar{ padding:0 18px; } .table-responsive{ overflow-x:auto; } .payment-summary{ grid-template-columns:1fr; } .stats-grid{ grid-template-columns:repeat(2,1fr); } .order-body{ grid-template-columns:1fr; } } /* ---------- Mobile ---------- */ @media (max-width:768px){ .sidebar{ position:fixed; left:-260px; width:260px; transition:.30s; z-index:9999; } .sidebar.show{ left:0; } .main-content{ margin-left:0; padding:15px; } .topbar{ flex-wrap:wrap; height:auto; padding:15px; gap:10px; } .topbar-left, .topbar-right{ width:100%; justify-content:space-between; } .dashboard-card{ padding:18px; } .dashboard-card h2{ font-size:26px; } .stats-grid{ grid-template-columns:1fr; } .payment-summary{ grid-template-columns:1fr; } .btn{ width:100%; } .btn-group{ flex-direction:column; } .quick-actions{ flex-direction:column; } .quick-actions .btn{ width:100%; } .order-header{ flex-direction:column; align-items:flex-start; } .form-row{ flex-direction:column; } .chat-body{ max-width:100%; } .upload-item{ flex-direction:column; align-items:flex-start; } .info-box{ flex-direction:column; text-align:center; } } /* ---------- Small Mobile ---------- */ @media (max-width:576px){ body{ font-size:13px; } h1{ font-size:24px; } h2{ font-size:22px; } h3{ font-size:20px; } h4{ font-size:18px; } .card{ border-radius:10px; } .card-body{ padding:15px; } .table th, .table td{ padding:10px; font-size:13px; } .login-card{ padding:25px; } .user-avatar{ width:36px; height:36px; font-size:14px; } .stats-number{ font-size:28px; } } /* ---------- Print ---------- */ @media print{ .sidebar, .topbar, .btn, .pagination, .search-box{ display:none!important; } .main-content{ margin:0; padding:0; } body{ background:#fff; } } /*================================================== FINAL UI IMPROVEMENTS ==================================================*/ /* ---------- Smooth Animation ---------- */ a, button, .btn, .card, .form-control, .form-select, .sidebar-item, .order-card, .dashboard-card{ transition: background-color .20s ease, color .20s ease, border-color .20s ease, box-shadow .20s ease, transform .20s ease; } /* ---------- Selection ---------- */ ::selection{ background:#2563eb; color:#fff; } /* ---------- Scrollbar ---------- */ ::-webkit-scrollbar{ width:10px; height:10px; } ::-webkit-scrollbar-track{ background:#edf2f7; } ::-webkit-scrollbar-thumb{ background:#94a3b8; border-radius:20px; } ::-webkit-scrollbar-thumb:hover{ background:#64748b; } /* Firefox */ *{ scrollbar-width:thin; scrollbar-color:#94a3b8 #edf2f7; } /* ---------- Focus ---------- */ *:focus-visible{ outline:none; box-shadow:0 0 0 3px rgba(37,99,235,.20); } /* ---------- HR ---------- */ hr{ border:none; border-top:1px solid #e5e7eb; margin:20px 0; } /* ---------- Code ---------- */ code{ background:#f1f5f9; color:#dc2626; padding:2px 6px; border-radius:6px; font-size:13px; } /* ---------- Pre ---------- */ pre{ background:#0f172a; color:#f8fafc; padding:18px; border-radius:10px; overflow:auto; } /* ---------- Cursor ---------- */ .pointer{ cursor:pointer; } /* ---------- Shadow ---------- */ .shadow-sm{ box-shadow:0 2px 8px rgba(0,0,0,.06); } .shadow{ box-shadow:0 6px 20px rgba(0,0,0,.10); } .shadow-lg{ box-shadow:0 12px 35px rgba(0,0,0,.18); } /* ---------- Radius ---------- */ .rounded{ border-radius:10px; } .rounded-lg{ border-radius:16px; } .rounded-circle{ border-radius:50%; } /* ---------- Flex ---------- */ .d-flex{ display:flex; } .align-center{ align-items:center; } .justify-between{ justify-content:space-between; } .justify-center{ justify-content:center; } .flex-column{ flex-direction:column; } .flex-wrap{ flex-wrap:wrap; } .gap-5{ gap:5px; } .gap-10{ gap:10px; } .gap-15{ gap:15px; } .gap-20{ gap:20px; } /* ---------- Width ---------- */ .w-25{ width:25%; } .w-50{ width:50%; } .w-75{ width:75%; } .w-100{ width:100%; } /* ---------- Height ---------- */ .h-100{ height:100%; } /* ---------- Margin ---------- */ .m-0{ margin:0; } .mt-5{ margin-top:5px; } .mt-10{ margin-top:10px; } .mt-15{ margin-top:15px; } .mt-20{ margin-top:20px; } .mb-5{ margin-bottom:5px; } .mb-10{ margin-bottom:10px; } .mb-15{ margin-bottom:15px; } .mb-20{ margin-bottom:20px; } .ms-auto{ margin-left:auto; } .me-auto{ margin-right:auto; } /* ---------- Padding ---------- */ .p-0{ padding:0; } .p-10{ padding:10px; } .p-15{ padding:15px; } .p-20{ padding:20px; } /* ---------- Text ---------- */ .text-center{ text-align:center; } .text-right{ text-align:right; } .text-left{ text-align:left; } .text-muted{ color:#6b7280; } .text-primary{ color:#2563eb; } .text-success{ color:#16a34a; } .text-danger{ color:#dc2626; } .text-warning{ color:#f59e0b; } /* ---------- Background ---------- */ .bg-light{ background:#f8fafc; } .bg-white{ background:#fff; } /* ---------- Image ---------- */ .img-fluid{ max-width:100%; height:auto; } /* ---------- Fade ---------- */ .fade-in{ animation:fadeIn .30s ease; } @keyframes fadeIn{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; transform:translateY(0); } } /* ---------- Footer ---------- */ .footer{ margin-top:30px; padding:20px; text-align:center; color:#94a3b8; font-size:13px; } ############################################################ # FILE : assets/js/app.js ############################################################ "use strict"; /*================================================== FlyMilestone Academic Portal app.js Version 2.0 ==================================================*/ document.addEventListener("DOMContentLoaded", function () { /*================================================== Bootstrap Tooltip ==================================================*/ document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) { new bootstrap.Tooltip(el); }); /*================================================== Bootstrap Popover ==================================================*/ document.querySelectorAll('[data-bs-toggle="popover"]').forEach(function (el) { new bootstrap.Popover(el); }); /*================================================== Auto Hide Alerts ==================================================*/ setTimeout(function () { document.querySelectorAll(".alert").forEach(function (alert) { alert.classList.add("fade"); setTimeout(function () { alert.remove(); }, 500); }); }, 5000); /*================================================== Order Tabs ==================================================*/ document.querySelectorAll(".order-tab").forEach(function (tab) { tab.addEventListener("click", function () { document.querySelectorAll(".order-tab").forEach(function (item) { item.classList.remove("active"); }); document.querySelectorAll(".tab-content").forEach(function (item) { item.classList.remove("active"); }); this.classList.add("active"); const target = this.dataset.tab; const content = document.getElementById(target); if (content) { content.classList.add("active"); } }); }); /*================================================== Confirm Delete ==================================================*/ document.querySelectorAll(".confirm-delete").forEach(function (button) { button.addEventListener("click", function (e) { if (!confirm("Bu əməliyyatı təsdiqləyirsiniz?")) { e.preventDefault(); } }); }); /*================================================== Auto Focus ==================================================*/ const autoFocus = document.querySelector("[autofocus]"); if (autoFocus) { autoFocus.focus(); } /*================================================== Sidebar Toggle ==================================================*/ const sidebarToggle = document.querySelector("[data-sidebar-toggle]"); const sidebar = document.querySelector(".sidebar"); if (sidebarToggle && sidebar) { sidebarToggle.addEventListener("click", function () { sidebar.classList.toggle("show"); }); } /*================================================== Close Sidebar On Mobile ==================================================*/ document.addEventListener("click", function (e) { if (window.innerWidth > 768) return; if (!sidebar) return; if ( !sidebar.contains(e.target) && !e.target.closest("[data-sidebar-toggle]") ) { sidebar.classList.remove("show"); } }); /*================================================== Scroll To Top ==================================================*/ const scrollTopButton = document.querySelector(".scroll-top"); if (scrollTopButton) { window.addEventListener("scroll", function () { if (window.scrollY > 300) { scrollTopButton.classList.add("show"); } else { scrollTopButton.classList.remove("show"); } }); scrollTopButton.addEventListener("click", function () { window.scrollTo({ top: 0, behavior: "smooth" }); }); } /*================================================== Copy To Clipboard ==================================================*/ document.querySelectorAll("[data-copy]").forEach(function (button) { button.addEventListener("click", function () { const text = this.dataset.copy; if (!text) { showWarning("Kopyalanacaq məlumat tapılmadı."); return; } navigator.clipboard.writeText(text) .then(function () { showSuccess("Məlumat kopyalandı."); }) .catch(function () { showError("Kopyalama mümkün olmadı."); }); }); }); /*================================================== Password Show / Hide ==================================================*/ document.querySelectorAll("[data-password-toggle]").forEach(function(button){ button.addEventListener("click",function(){ const input=document.querySelector(this.dataset.passwordToggle); if(!input){ return; } if(input.type==="password"){ input.type="text"; this.innerHTML=''; }else{ input.type="password"; this.innerHTML=''; } }); }); /*================================================== Prevent Double Submit ==================================================*/ document.querySelectorAll("form").forEach(function(form){ form.addEventListener("submit",function(event){ const submitButton=form.querySelector( 'button[type="submit"],input[type="submit"]' ); if(!submitButton){ return; } if(submitButton.disabled){ event.preventDefault(); return; } submitButton.disabled=true; submitButton.style.pointerEvents = "none"; submitButton.style.opacity = ".7"; submitButton.dataset.originalHtml = submitButton.innerHTML; if(submitButton.tagName==="BUTTON"){ submitButton.innerHTML=' Zəhmət olmasa gözləyin...'; } }); }); /*================================================== Enter Key Submit ==================================================*/ document.querySelectorAll("input").forEach(function(input){ input.addEventListener("keypress",function(e){ if(e.key!=="Enter"){ return; } const form=this.closest("form"); if(form){ form.requestSubmit(); } }); }); /*================================================== Auto Select Input ==================================================*/ document.querySelectorAll("[data-select-all]").forEach(function(input){ input.addEventListener("focus",function(){ this.select(); }); }); /*================================================== Live Table Search ==================================================*/ document.querySelectorAll("[data-table-search]").forEach(function(input){ input.addEventListener("keyup",function(){ const keyword=this.value.toLowerCase().trim(); const table=document.querySelector(this.dataset.tableSearch); if(!table){ return; } const rows=table.querySelectorAll("tbody tr"); let visibleRows=0; rows.forEach(function(row){ const text=row.innerText.toLowerCase(); if(text.indexOf(keyword)>-1){ row.style.display=""; visibleRows++; }else{ row.style.display="none"; } }); let emptyRow=table.querySelector(".table-search-empty"); if(visibleRows===0){ if(!emptyRow){ emptyRow=document.createElement("tr"); emptyRow.className="table-search-empty"; emptyRow.innerHTML= '' + ' Nəticə tapılmadı.' + ''; table.querySelector("tbody").appendChild(emptyRow); } }else{ if(emptyRow){ emptyRow.remove(); } } }); }); /*================================================== Auto Uppercase ==================================================*/ document.querySelectorAll("[data-uppercase]").forEach(function(input){ input.addEventListener("input",function(){ this.value=this.value.toUpperCase(); }); }); /*================================================== Auto Lowercase ==================================================*/ document.querySelectorAll("[data-lowercase]").forEach(function(input){ input.addEventListener("input",function(){ this.value=this.value.toLowerCase(); }); }); /*================================================== Character Counter ==================================================*/ document.querySelectorAll("[maxlength]").forEach(function(input){ const counter=document.createElement("small"); counter.className="text-muted"; counter.style.display="block"; counter.style.marginTop="5px"; input.after(counter); function updateCounter(){ counter.innerHTML= input.value.length+ " / "+ input.maxLength; } updateCounter(); input.addEventListener("input",updateCounter); }); /*================================================== Auto Resize Textarea ==================================================*/ document.querySelectorAll("textarea").forEach(function(textarea){ textarea.addEventListener("input",function(){ this.style.height="auto"; this.style.height=this.scrollHeight+"px"; }); }); /*================================================== Loading Overlay ==================================================*/ window.showLoading = function () { let loading = document.getElementById("loadingOverlay"); if (!loading) { loading = document.createElement("div"); loading.id = "loadingOverlay"; loading.innerHTML = `
`; document.body.appendChild(loading); } loading.style.display = "flex"; }; window.hideLoading = function () { const loading = document.getElementById("loadingOverlay"); if (loading) { loading.style.display = "none"; } }; /*================================================== AJAX Submit Buttons ==================================================*/ document.querySelectorAll("[data-loading]").forEach(function(button){ button.addEventListener("click",function(){ showLoading(); }); }); /*================================================== Disable Empty Links ==================================================*/ document.querySelectorAll('a[href="#"]').forEach(function(link){ link.addEventListener("click",function(e){ e.preventDefault(); }); }); /*================================================== External Links ==================================================*/ document.querySelectorAll("a[target='_blank']").forEach(function(link){ link.setAttribute("rel","noopener noreferrer"); }); /*================================================== Number Only ==================================================*/ document.querySelectorAll("[data-number]").forEach(function(input){ input.addEventListener("input",function(){ this.value=this.value.replace(/[^0-9]/g,""); }); }); /*================================================== Decimal Number ==================================================*/ document.querySelectorAll("[data-decimal]").forEach(function(input){ input.addEventListener("input",function(){ this.value=this.value.replace(/[^0-9.]/g,""); }); }); /*================================================== Trim Inputs ==================================================*/ document.querySelectorAll("input[type='text'], textarea").forEach(function(input){ input.addEventListener("blur",function(){ this.value=this.value.trim(); }); }); /*================================================== Toast Notification System ==================================================*/ const toastContainerId = "fm-toast-container"; function getToastContainer() { let container = document.getElementById(toastContainerId); if (!container) { container = document.createElement("div"); container.id = toastContainerId; container.style.position = "fixed"; container.style.top = "20px"; container.style.right = "20px"; container.style.zIndex = "99999"; container.style.display = "flex"; container.style.flexDirection = "column"; container.style.gap = "10px"; document.body.appendChild(container); } return container; } function showToast(type, message) { const colors = { success: "#16a34a", error: "#dc2626", warning: "#f59e0b", info: "#2563eb" }; const icons = { success: "fa-circle-check", error: "fa-circle-xmark", warning: "fa-triangle-exclamation", info: "fa-circle-info" }; const toast = document.createElement("div"); toast.style.background = "#ffffff"; toast.style.borderLeft = "5px solid " + colors[type]; toast.style.padding = "15px 18px"; toast.style.borderRadius = "10px"; toast.style.minWidth = "320px"; toast.style.boxShadow = "0 8px 25px rgba(0,0,0,.12)"; toast.style.display = "flex"; toast.style.alignItems = "center"; toast.style.gap = "12px"; toast.style.opacity = "0"; toast.style.transform = "translateX(40px)"; toast.style.transition = ".25s"; toast.innerHTML = '' + '
' + message + '
'; const container = getToastContainer(); container.appendChild(toast); requestAnimationFrame(function () { toast.style.opacity = "1"; toast.style.transform = "translateX(0)"; }); setTimeout(function () { toast.style.opacity = "0"; toast.style.transform = "translateX(40px)"; setTimeout(function () { toast.remove(); }, 250); }, 4000); } window.showSuccess = function (message) { showToast("success", message); }; window.showError = function (message) { showToast("error", message); }; window.showWarning = function (message) { showToast("warning", message); }; window.showInfo = function (message) { showToast("info", message); }; /*================================================== Loading Button ==================================================*/ window.buttonLoading = function (button, text = "Zəhmət olmasa gözləyin...") { if (!button) return; if (!button.dataset.originalHtml) { button.dataset.originalHtml = button.innerHTML; } button.disabled = true; button.innerHTML = ' ' + text; }; window.buttonReset = function (button) { if (!button) return; if (button.dataset.originalHtml) { button.innerHTML = button.dataset.originalHtml; } button.disabled = false; button.style.pointerEvents = ""; button.style.opacity = ""; delete button.dataset.originalHtml; delete button.dataset.originalText; }; /*================================================== Format File Size ==================================================*/ window.formatFileSize = function (bytes) { if (bytes === 0) { return "0 B"; } const units = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return (bytes / Math.pow(1024, i)).toFixed(2) + " " + units[i]; }; /*================================================== Preview Selected File Name ==================================================*/ document.querySelectorAll("[data-file-input]").forEach(function (input) { input.addEventListener("change", function () { const target = document.querySelector(this.dataset.fileInput); if (!target) return; if (!this.files.length) { target.innerHTML = ""; return; } let html = ""; Array.from(this.files).forEach(function (file) { html += '
' + ' ' + file.name + ' (' + formatFileSize(file.size) + ')
'; }); target.innerHTML = html; }); }); /*================================================== Auto Current Year ==================================================*/ document.querySelectorAll("[data-current-year]").forEach(function (item) { item.textContent = new Date().getFullYear(); }); /*================================================== Page Reload Button ==================================================*/ document.querySelectorAll("[data-reload]").forEach(function (button) { button.addEventListener("click", function () { location.reload(); }); }); /*================================================== Print Page ==================================================*/ document.querySelectorAll("[data-print]").forEach(function (button) { button.addEventListener("click", function () { window.print(); }); }); /*================================================== Go Back ==================================================*/ document.querySelectorAll("[data-back]").forEach(function (button) { button.addEventListener("click", function () { history.back(); }); }); /*================================================== AJAX Helper ==================================================*/ window.ajax = async function (options) { const settings = Object.assign({ url: "", method: "POST", data: {}, loading: true, success: null, error: null }, options); try { if (settings.loading) { showLoading(); } const formData = new FormData(); Object.keys(settings.data).forEach(function (key) { formData.append(key, settings.data[key]); }); const response = await fetch(settings.url, { method: settings.method, body: formData, credentials: "same-origin" }); if (!response.ok) { throw new Error("HTTP " + response.status); } const result = await response.json(); if (settings.loading) { hideLoading(); } if (result.success) { if (typeof settings.success === "function") { settings.success(result); } } else { if (typeof settings.error === "function") { settings.error(result); } else { showError(result.message || "Xəta baş verdi."); } } } catch (error) { if (settings.loading) { hideLoading(); } console.error(error); showError("Server ilə əlaqə qurulmadı."); } }; /*================================================== AJAX Forms ==================================================*/ document.querySelectorAll("[data-ajax-form]").forEach(function(form){ form.addEventListener("submit",function(e){ e.preventDefault(); ajax({ url: form.action, data: Object.fromEntries( new FormData(form) ), success:function(response){ if(response.message){ showSuccess(response.message); } if(response.redirect){ window.location=response.redirect; } }, error:function(response){ showError( response.message || "Xəta baş verdi." ); } }); }); }); /*================================================== AJAX Buttons ==================================================*/ document.querySelectorAll("[data-ajax]").forEach(function(button){ button.addEventListener("click",function(){ ajax({ url:this.dataset.ajax, data:{ id:this.dataset.id || "" }, success:function(response){ if(response.message){ showSuccess(response.message); } if(response.reload){ location.reload(); } } }); }); }); /*================================================== Reload Table ==================================================*/ window.reloadPage=function(){ location.reload(); }; /*================================================== Notification Badge ==================================================*/ window.setNotificationCount = function (count) { document.querySelectorAll("[data-notification-count]").forEach(function (badge) { badge.textContent = count; if (count > 0) { badge.style.display = "inline-flex"; } else { badge.style.display = "none"; } }); }; /*================================================== Notification Dropdown Read ==================================================*/ document.querySelectorAll("[data-notification-read]").forEach(function(item){ item.addEventListener("click",function(){ this.classList.remove("unread"); }); }); /*================================================== Auto Refresh Elements ==================================================*/ document.querySelectorAll("[data-auto-refresh]").forEach(function(element){ const seconds = parseInt( element.dataset.autoRefresh ) || 60; setInterval(function(){ if(element.dataset.refreshUrl){ fetch(element.dataset.refreshUrl) .then(function(response){ return response.text(); }) .then(function(html){ element.innerHTML = html; }) .catch(function(error){ console.error(error); }); } }, seconds * 1000); }); /*================================================== Session Timeout Warning ==================================================*/ let sessionTimer; function resetSessionTimer(){ clearTimeout(sessionTimer); sessionTimer = setTimeout(function(){ showWarning( "Uzun müddətdir aktivlik yoxdur." ); }, 25 * 60 * 1000); } [ "click", "mousemove", "keydown", "scroll" ].forEach(function(eventName){ document.addEventListener( eventName, resetSessionTimer ); }); resetSessionTimer(); /*================================================== Online / Offline Status ==================================================*/ function updateConnectionStatus(){ if(navigator.onLine){ showInfo("İnternet bağlantısı bərpa olundu."); }else{ showWarning("İnternet bağlantısı kəsildi."); } } window.addEventListener( "online", updateConnectionStatus ); window.addEventListener( "offline", updateConnectionStatus ); /*================================================== Auto Clock ==================================================*/ document.querySelectorAll("[data-live-clock]").forEach(function(clock){ function updateClock(){ const now = new Date(); clock.textContent = now.toLocaleTimeString(); } updateClock(); setInterval(updateClock,1000); }); /*================================================== Email Validation ==================================================*/ window.isValidEmail = function (email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }; /*================================================== URL Validation ==================================================*/ window.isValidUrl = function (url) { try { new URL(url); return true; } catch { return false; } }; /*================================================== Escape HTML ==================================================*/ window.escapeHtml = function (text) { const div = document.createElement("div"); div.textContent = text; return div.innerHTML; }; /*================================================== Random String ==================================================*/ window.randomString = function (length = 12) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; let result = ""; for (let i = 0; i < length; i++) { result += chars.charAt( Math.floor(Math.random() * chars.length) ); } return result; }; /*================================================== Slug Generator ==================================================*/ window.slugify = function (text) { return text .toString() .toLowerCase() .trim() .replace(/\s+/g, "-") .replace(/[^\w\-]+/g, "") .replace(/\-\-+/g, "-"); }; /*================================================== Debounce ==================================================*/ window.debounce = function (callback, delay = 300) { let timer; return function () { clearTimeout(timer); const args = arguments; const context = this; timer = setTimeout(function () { callback.apply(context, args); }, delay); }; }; /*================================================== Throttle ==================================================*/ window.throttle = function (callback, delay = 300) { let waiting = false; return function () { if (waiting) { return; } callback.apply(this, arguments); waiting = true; setTimeout(function () { waiting = false; }, delay); }; }; /*================================================== Console Signature ==================================================*/ console.log( "%cFlyMilestone Academic Portal v2.0", "color:#2563eb;font-size:16px;font-weight:bold;" ); }); ############################################################ # FILE : assets/js/chat.js ############################################################ document.addEventListener("DOMContentLoaded",function(){ const form=document.getElementById("chat-form"); const box=document.getElementById("chat-box"); if(!form || !box){ return; } function loadMessages(){ const orderId=form.querySelector("[name=order_id]").value; fetch("ajax/messages/load-messages.php?order_id="+orderId) .then(response=>response.text()) .then(html=>{ box.innerHTML=html; box.scrollTop=box.scrollHeight; }); } loadMessages(); setInterval(loadMessages,5000); form.addEventListener("submit",function(e){ e.preventDefault(); const data=new FormData(form); fetch( "ajax/messages/send-message.php", { method:"POST", body:data } ) .then(response=>response.text()) .then(result=>{ if(result.trim()=="OK"){ form.reset(); let files=document.getElementById("selected-files"); if(files){ files.innerHTML=""; } loadMessages(); }else{ alert(result); } }); }); }); ############################################################ # FILE : assets/js/modal.js ############################################################ document.addEventListener("DOMContentLoaded",function(){ document.querySelectorAll("[data-modal]").forEach(function(button){ button.addEventListener("click",function(){ let id=this.dataset.modal; let modal=document.getElementById(id); if(modal){ let bs=new bootstrap.Modal(modal); bs.show(); } }); }); document.querySelectorAll("[data-modal-close]").forEach(function(button){ button.addEventListener("click",function(){ let modal=bootstrap.Modal.getInstance( button.closest(".modal") ); if(modal){ modal.hide(); } }); }); document.addEventListener("keydown",function(e){ if(e.key==="Escape"){ document.querySelectorAll(".modal.show").forEach(function(item){ let modal=bootstrap.Modal.getInstance(item); if(modal){ modal.hide(); } }); } }); }); ############################################################ # FILE : assets/js/pagination.js ############################################################ document.addEventListener("DOMContentLoaded",function(){ document.querySelectorAll(".pagination a").forEach(function(link){ link.addEventListener("click",function(e){ if(this.dataset.ajax!=="1"){ return; } e.preventDefault(); fetch(this.href) .then(function(response){ return response.text(); }) .then(function(html){ let target=document.querySelector(link.dataset.target); if(target){ target.innerHTML=html; window.scrollTo({ top:0, behavior:"smooth" }); } }); }); }); }); ############################################################ # FILE : assets/js/search.js ############################################################ document.addEventListener("DOMContentLoaded",function(){ document.querySelectorAll(".table-search").forEach(function(input){ input.addEventListener("keyup",function(){ let value=this.value.toLowerCase(); let table=document.querySelector(this.dataset.table); if(!table){ return; } table.querySelectorAll("tbody tr").forEach(function(row){ let text=row.innerText.toLowerCase(); row.style.display=text.indexOf(value)>-1 ? "" : "none"; }); }); }); }); ############################################################ # FILE : assets/js/upload.js ############################################################ document.addEventListener("DOMContentLoaded",function(){ const input=document.getElementById("chat_files"); const list=document.getElementById("selected-files"); if(!input || !list){ return; } input.addEventListener("change",function(){ list.innerHTML=""; if(this.files.length===0){ return; } Array.from(this.files).forEach(function(file){ let item=document.createElement("div"); item.className="upload-item"; item.innerHTML= ' ' +file.name+ ' ('+ formatSize(file.size)+ ')'; list.appendChild(item); }); }); function formatSize(bytes){ if(bytes>=1073741824){ return (bytes/1073741824).toFixed(2)+" GB"; } if(bytes>=1048576){ return (bytes/1048576).toFixed(2)+" MB"; } if(bytes>=1024){ return (bytes/1024).toFixed(2)+" KB"; } return bytes+" B"; } }); ############################################################ # FILE : check-project.php ############################################################ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, PDO::ATTR_PERSISTENT => false ]; try { $pdo = new PDO( $dsn, DB_USER, DB_PASS, $options ); } catch (PDOException $e) { if (defined('APP_DEBUG') && APP_DEBUG) { exit( '

Database Connection Error

' . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8') . '

' ); } exit('Database connection failed.'); } ############################################################ # FILE : customer/dashboard.php ############################################################ prepare(" SELECT COUNT(*) FROM orders WHERE customer_id=? "); $stmt->execute([ currentUserId() ]); $totalOrders=$stmt->fetchColumn(); $stmt=$pdo->prepare(" SELECT COUNT(*) FROM orders WHERE customer_id=? AND status='IN_PROGRESS' "); $stmt->execute([ currentUserId() ]); $activeOrders=$stmt->fetchColumn(); $stmt=$pdo->prepare(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE customer_id=? AND status='PAID' "); $stmt->execute([ currentUserId() ]); $totalPayments=$stmt->fetchColumn(); ?>
Total Orders

Active Orders

Total Paid

prepare(" SELECT o.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM orders o LEFT JOIN users c ON c.id=o.customer_id LEFT JOIN users w ON w.id=o.worker_id WHERE o.id=? AND o.customer_id=? LIMIT 1 "); $stmt->execute([ $id, currentUserId() ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>


Worker
Status
Priority
Deadline
Description
prepare(" SELECT COUNT(*) FROM orders o {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT o.*, CONCAT( u.first_name, ' ', u.last_name ) worker_name FROM orders o LEFT JOIN users u ON u.id=o.worker_id {$where} ORDER BY o.id DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); $orders=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

My Orders

# Order No Title Worker Status Priority Deadline Open
Open
1): ?>
prepare(" SELECT * FROM users WHERE id = ? LIMIT 1 "); $stmt->execute([ currentUserId() ]); $user = $stmt->fetch(); return $user ?: null; } /* |-------------------------------------------------------------------------- | Roles |-------------------------------------------------------------------------- */ function isSuperAdmin(): bool { return currentRoleId() === ROLE_SUPER_ADMIN; } function isAdmin(): bool { return currentRoleId() === ROLE_ADMIN; } function isWorker(): bool { return currentRoleId() === ROLE_WORKER; } function isCustomer(): bool { return currentRoleId() === ROLE_CUSTOMER; } /* |-------------------------------------------------------------------------- | Session |-------------------------------------------------------------------------- */ function regenerateSession(): void { if (isLoggedIn()) { session_regenerate_id(true); } } function logout(): void { $_SESSION = []; if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie( session_name(), '', time() - 42000, $params['path'], $params['domain'], $params['secure'], $params['httponly'] ); } session_destroy(); } /* |-------------------------------------------------------------------------- | Session Timeout |-------------------------------------------------------------------------- */ if (isLoggedIn()) { $timeout = 60 * 60 * 4; if (!empty($_SESSION['last_activity'])) { if ((time() - $_SESSION['last_activity']) > $timeout) { logout(); header('Location: ' . SITE_URL . '/login.php?expired=1'); exit; } } $_SESSION['last_activity'] = time(); } ############################################################ # FILE : includes/files/files-table.php ############################################################ prepare(" SELECT f.*, u.first_name, u.last_name, r.name role_name FROM order_files f INNER JOIN users u ON u.id=f.uploaded_by INNER JOIN roles r ON r.id=u.role_id WHERE f.order_id=? AND f.is_deleted=0 ORDER BY f.created_at DESC "); $stmt->execute([ $orderId ]); ?>
fetch()): ?>
# File Name Uploaded By Role Size Date Customer Worker Download
############################################################ # FILE : includes/functions.php ############################################################ =1073741824){ return number_format( $bytes/1073741824, 2 ).' GB'; } if($bytes>=1048576){ return number_format( $bytes/1048576, 2 ).' MB'; } if($bytes>=1024){ return number_format( $bytes/1024, 2 ).' KB'; } return $bytes.' B'; } function randomString(int $length=32):string{ return substr( bin2hex( random_bytes( $length ) ), 0, $length ); } ############################################################ # FILE : includes/layout/footer.php ############################################################ ############################################################ # FILE : includes/layout/header.php ############################################################ <?= e(SITE_NAME); ?>
############################################################ # FILE : includes/layout/sidebar.php ############################################################
############################################################ # FILE : includes/log.php ############################################################ prepare(" INSERT INTO logs ( user_id, order_id, module, action, description, old_data, new_data, ip_address, user_agent, created_at ) VALUES ( ?,?,?,?,?,?,?,?, ?,NOW() ) "); $stmt->execute([ currentUserId(), $orderId, $module, $action, $description, $oldData ? json_encode($oldData,JSON_UNESCAPED_UNICODE) : null, $newData ? json_encode($newData,JSON_UNESCAPED_UNICODE) : null, clientIp(), userAgent() ]); } ############################################################ # FILE : includes/messages/message-form.php ############################################################
############################################################ # FILE : includes/messages/message-list.php ############################################################ prepare(" SELECT m.id, m.message, m.created_at, m.sender_id, m.visible_customer, m.visible_worker, m.has_files, u.first_name, u.last_name, r.name role_name, r.color role_color FROM messages m INNER JOIN users u ON u.id=m.sender_id INNER JOIN roles r ON r.id=u.role_id WHERE m.order_id=? ORDER BY m.created_at ASC "); $stmt->execute([ $orderId ]); ?>
fetch()): ?>
()
prepare(" SELECT * FROM order_files WHERE message_id=? AND is_deleted=0 ORDER BY id "); $fileStmt->execute([ $row['id'] ]); while($file=$fileStmt->fetch()): ?>
############################################################ # FILE : includes/notification.php ############################################################ prepare(" INSERT INTO notifications ( user_id, order_id, sender_id, title, message, url, icon, color, created_at ) VALUES ( ?,?,?,?,?,?,?,?,NOW() ) "); $stmt->execute([ $userId, $orderId, $senderId ?: currentUserId(), $title, $message, $url, $icon, $color ]); } function notificationCount(int $userId):int{ global $pdo; $stmt=$pdo->prepare(" SELECT COUNT(*) FROM notifications WHERE user_id=? AND is_read=0 "); $stmt->execute([ $userId ]); return (int)$stmt->fetchColumn(); } function markNotificationRead(int $id):void{ global $pdo; $stmt=$pdo->prepare(" UPDATE notifications SET is_read=1, read_at=NOW() WHERE id=? LIMIT 1 "); $stmt->execute([ $id ]); } ############################################################ # FILE : includes/payments/payment-table.php ############################################################ prepare(" SELECT p.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name, CONCAT(a.first_name,' ',a.last_name) confirmed_name FROM payments p LEFT JOIN users c ON c.id=p.customer_id LEFT JOIN users w ON w.id=p.worker_id LEFT JOIN users a ON a.id=p.confirmed_by WHERE p.order_id=? ORDER BY p.payment_date DESC,p.id DESC "); $stmt->execute([ $orderId ]); ?>
fetch()): ?>
# Payment No Type Amount Method Status Date Confirmed By Confirm
############################################################ # FILE : includes/permissions/permission.php ############################################################ prepare(" SELECT rp.id FROM role_permissions rp INNER JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=? AND p.permission_key=? AND p.status='ACTIVE' LIMIT 1 "); $stmt->execute([ currentRoleId(), $permission ]); if(!$stmt->fetch()){ http_response_code(403); exit('403 Forbidden'); } } function can(string $permission):bool{ global $pdo; if(isSuperAdmin()){ return true; } $stmt=$pdo->prepare(" SELECT rp.id FROM role_permissions rp INNER JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=? AND p.permission_key=? AND p.status='ACTIVE' LIMIT 1 "); $stmt->execute([ currentRoleId(), $permission ]); return (bool)$stmt->fetch(); } ############################################################ # FILE : includes/security.php ############################################################ '; } /* |-------------------------------------------------------------------------- | CSRF Validation |-------------------------------------------------------------------------- */ function csrf_validate(): void { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { return; } $token = $_POST[CSRF_TOKEN] ?? ''; if ( empty($token) || empty($_SESSION[CSRF_TOKEN]) || !hash_equals($_SESSION[CSRF_TOKEN], $token) ) { http_response_code(403); exit('CSRF Token Mismatch'); } } /* |-------------------------------------------------------------------------- | Random String |-------------------------------------------------------------------------- */ function random_string(int $length = 32): string { return substr( bin2hex(random_bytes($length)), 0, $length ); } /* |-------------------------------------------------------------------------- | Request Method |-------------------------------------------------------------------------- */ function is_post(): bool { return $_SERVER['REQUEST_METHOD'] === 'POST'; } function is_get(): bool { return $_SERVER['REQUEST_METHOD'] === 'GET'; } /* |-------------------------------------------------------------------------- | Security Headers |-------------------------------------------------------------------------- */ header('X-Frame-Options: SAMEORIGIN'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: strict-origin-when-cross-origin'); header('X-XSS-Protection: 1; mode=block'); /* |-------------------------------------------------------------------------- | Validate CSRF |-------------------------------------------------------------------------- */ csrf_validate(); ############################################################ # FILE : includes/timeline/timeline-table.php ############################################################ prepare(" SELECT t.*, u.first_name, u.last_name, r.name role_name, r.color role_color FROM timeline t LEFT JOIN users u ON u.id=t.user_id LEFT JOIN roles r ON r.id=u.role_id WHERE t.order_id=? ORDER BY t.created_at DESC "); $stmt->execute([ $orderId ]); ?> fetch()): ?>
()
############################################################ # FILE : includes/timeline.php ############################################################ prepare(" INSERT INTO timeline ( order_id, user_id, event_type, title, description, icon, color, ip_address, user_agent, created_at ) VALUES ( ?,?,?,?,?,?,?,?, ?,NOW() ) "); $stmt->execute([ $orderId, currentUserId(), $eventType, $title, $description, $icon, $color, clientIp(), userAgent() ]); } ############################################################ # FILE : includes/upload.php ############################################################ MAX_UPLOAD_SIZE){ return false; } $extension=strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) ); if( !in_array( $extension, ALLOWED_EXTENSIONS, true ) ){ return false; } $folder= ORDER_UPLOAD_PATH. $orderNo. '/'; if(!is_dir($folder)){ mkdir( $folder, 0777, true ); } $originalName= pathinfo( $file['name'], PATHINFO_FILENAME ); $safeName=iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $originalName ); $safeName=preg_replace( '/[^A-Za-z0-9_-]/', '_', $safeName ); $storedName= strtolower($safeName). '_'. time(). rand(1000,9999). '.'. $extension; $destination= $folder. $storedName; if( !move_uploaded_file( $file['tmp_name'], $destination ) ){ return false; } return [ 'real_name'=>$file['name'], 'stored_name'=>$storedName, 'extension'=>$extension, 'mime_type'=>mime_content_type($destination), 'file_size'=>$file['size'], 'folder'=>$folder ]; } function deleteUploadedFile( string $folder, string $storedName ):void{ $path= rtrim($folder,'/'). '/'. $storedName; if(file_exists($path)){ @unlink($path); } } ############################################################ # FILE : login.php ############################################################ prepare(" SELECT * FROM users WHERE username=? AND status='ACTIVE' LIMIT 1 "); $stmt->execute([ $username ]); $user=$stmt->fetch(); if( $user && password_verify( $password, $user['password'] ) ){ $_SESSION['user_id']=$user['id']; $_SESSION['role_id']=$user['role_id']; $pdo->prepare(" UPDATE users SET last_login_at=NOW(), last_login_ip=? WHERE id=? ") ->execute([ clientIp(), $user['id'] ]); switch($user['role_id']){ case ROLE_SUPER_ADMIN: case ROLE_ADMIN: redirect('admin/dashboard.php'); break; case ROLE_WORKER: redirect('worker/dashboard.php'); break; case ROLE_CUSTOMER: redirect('customer/dashboard.php'); break; } } error('Username və ya Password yanlışdır.'); } include 'includes/layout/header.php'; ?>

"; echo ""; echo ""; echo "Project Structure"; echo ""; echo ""; echo "

Project Structure

"; echo "
";

function tree($dir, $prefix = '')
{
    $items = array_diff(scandir($dir), ['.', '..']);

    natcasesort($items);

    $items = array_values($items);

    $count = count($items);

    foreach ($items as $index => $item) {

        $path = $dir . DIRECTORY_SEPARATOR . $item;

        $last = ($index == $count - 1);

        echo $prefix;

        echo $last ? "└── " : "├── ";

        if (is_dir($path)) {

            echo "{$item}/\n";

            tree(
                $path,
                $prefix . ($last ? "    " : "│   ")
            );

        } else {

            echo "{$item}\n";

        }

    }
}

echo basename($root) . "/\n";

tree($root);

echo "
"; echo ""; ############################################################ # FILE : worker/dashboard.php ############################################################ prepare(" SELECT COUNT(*) FROM orders WHERE worker_id=? "); $stmt->execute([ currentUserId() ]); $totalOrders=$stmt->fetchColumn(); $stmt=$pdo->prepare(" SELECT COUNT(*) FROM orders WHERE worker_id=? AND status='IN_PROGRESS' "); $stmt->execute([ currentUserId() ]); $inProgress=$stmt->fetchColumn(); $stmt=$pdo->prepare(" SELECT IFNULL(SUM(amount),0) FROM payments WHERE worker_id=? AND status='PAID' "); $stmt->execute([ currentUserId() ]); $totalPayments=$stmt->fetchColumn(); ?>
Total Orders

In Progress

Total Income

prepare(" SELECT o.*, CONCAT(c.first_name,' ',c.last_name) customer_name, CONCAT(w.first_name,' ',w.last_name) worker_name FROM orders o LEFT JOIN users c ON c.id=o.customer_id LEFT JOIN users w ON w.id=o.worker_id WHERE o.id=? AND o.worker_id=? LIMIT 1 "); $stmt->execute([ $id, currentUserId() ]); $order=$stmt->fetch(); if(!$order){ error('Order not found.'); redirect('orders.php'); } include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>


Customer
Status
Priority
Deadline
Description
prepare(" SELECT COUNT(*) FROM orders o {$where} "); $stmt->execute($params); $totalRows=$stmt->fetchColumn(); $totalPages=max(1,ceil($totalRows/$limit)); $stmt=$pdo->prepare(" SELECT o.*, CONCAT( u.first_name, ' ', u.last_name ) customer_name FROM orders o INNER JOIN users u ON u.id=o.customer_id {$where} ORDER BY o.id DESC LIMIT {$offset},{$limit} "); $stmt->execute($params); $orders=$stmt->fetchAll(); include '../includes/layout/header.php'; include '../includes/layout/sidebar.php'; ?>

My Orders

# Order No Title Customer Status Priority Deadline Open
Open
1): ?>