Task Management System IN HTML CSS AND JAVASCRIPT WITH PHP AND MY SQL

Introduction to the Project

The Task Management System is a comprehensive, full-stack web application designed to help individuals and teams organize, track, and manage their tasks and projects efficiently. This system provides a powerful platform for creating tasks, setting priorities, tracking progress, collaborating with team members, and meeting deadlines.

The application features role-based access control with multiple user types: Admin, Project Managers, Team Members, and Clients. The system includes kanban boards, Gantt charts, time tracking, notifications, and detailed reporting for project insights.

Key Features

Admin Features

  • Dashboard Overview: System-wide statistics on users, projects, and tasks
  • User Management: Create, edit, and manage user accounts and permissions
  • Team Management: Create teams and assign members
  • Project Oversight: View all projects across the organization
  • System Settings: Configure global parameters and notification settings
  • Audit Logs: Track all system activities
  • Backup & Restore: Database backup management

Project Manager Features

  • Project Dashboard: Overview of all managed projects
  • Project Creation: Create new projects with timelines and budgets
  • Task Assignment: Assign tasks to team members with deadlines
  • Progress Tracking: Monitor project progress with Gantt charts
  • Resource Management: Allocate resources and track workload
  • Milestone Management: Set and track project milestones
  • Budget Tracking: Monitor project budgets and expenses
  • Report Generation: Generate project status reports

Team Member Features

  • Personal Dashboard: View assigned tasks and deadlines
  • Task Management: Update task status, add comments, attach files
  • Time Tracking: Log time spent on tasks
  • Calendar View: See tasks and deadlines in calendar format
  • Notifications: Receive updates on task assignments and changes
  • Collaboration: Comment on tasks, mention team members
  • File Sharing: Upload and share files related to tasks

Client Features

  • Project View: See project progress and milestones
  • Task Tracking: Monitor tasks relevant to client
  • File Access: View and download shared documents
  • Feedback: Provide feedback on deliverables
  • Communication: Message project team members

General Features

  • Kanban Boards: Visual task management with drag-and-drop
  • Gantt Charts: Timeline view of projects and dependencies
  • Priority Levels: High, medium, low priority settings
  • Task Categories: Categorize tasks by type
  • Tags & Labels: Organize tasks with custom tags
  • Search & Filters: Advanced search and filtering options
  • File Attachments: Upload files to tasks
  • Comments & Discussions: Threaded comments on tasks
  • Mentions: @mention team members in comments
  • Notifications: Email and in-app notifications
  • Activity Logs: Track all changes and activities
  • Export Options: Export data to PDF, Excel, CSV

Technology Stack

  • Frontend: HTML5, CSS3, JavaScript (ES6+), Bootstrap 5
  • Backend: PHP 8.0+ (Core PHP with MVC-like structure)
  • Database: MySQL 5.7+
  • Additional Libraries:
  • PHPMailer for email notifications
  • Bootstrap 5 for responsive UI
  • Font Awesome for icons
  • jQuery for AJAX operations
  • Select2 for enhanced dropdowns
  • DataTables for advanced tables
  • Chart.js for analytics
  • FullCalendar.js for calendar view
  • Dragula for drag-and-drop kanban
  • Moment.js for date handling
  • TCPDF for PDF generation
  • PhpSpreadsheet for Excel export

Project File Structure

task-management-system/
│
├── assets/
│   ├── css/
│   │   ├── style.css
│   │   ├── dashboard.css
│   │   ├── kanban.css
│   │   ├── calendar.css
│   │   ├── responsive.css
│   │   └── dark-mode.css
│   ├── js/
│   │   ├── main.js
│   │   ├── dashboard.js
│   │   ├── kanban.js
│   │   ├── calendar.js
│   │   ├── gantt.js
│   │   ├── task.js
│   │   ├── validation.js
│   │   └── charts.js
│   ├── images/
│   │   ├── avatars/
│   │   └── icons/
│   └── plugins/
│       ├── datatables/
│       ├── fullcalendar/
│       ├── select2/
│       └── dragula/
│
├── includes/
│   ├── config.php
│   ├── Database.php
│   ├── functions.php
│   ├── auth.php
│   ├── User.php
│   ├── Project.php
│   ├── Task.php
│   ├── Team.php
│   ├── Comment.php
│   ├── Notification.php
│   ├── ActivityLog.php
│   ├── Report.php
│   └── helpers/
│       ├── DateHelper.php
│       ├── FileHelper.php
│       └── ValidationHelper.php
│
├── admin/
│   ├── dashboard.php
│   ├── manage_users.php
│   ├── add_user.php
│   ├── edit_user.php
│   ├── manage_teams.php
│   ├── create_team.php
│   ├── edit_team.php
│   ├── projects.php
│   ├── settings.php
│   ├── audit_logs.php
│   └── backup.php
│
├── manager/
│   ├── dashboard.php
│   ├── projects.php
│   ├── create_project.php
│   ├── edit_project.php
│   ├── project_details.php
│   ├── tasks.php
│   ├── create_task.php
│   ├── task_details.php
│   ├── gantt.php
│   ├── team.php
│   ├── team_members.php
│   ├── reports.php
│   └── budget.php
│
├── member/
│   ├── dashboard.php
│   ├── my_tasks.php
│   ├── task_details.php
│   ├── update_task.php
│   ├── calendar.php
│   ├── time_tracking.php
│   ├── files.php
│   ├── notifications.php
│   └── profile.php
│
├── client/
│   ├── dashboard.php
│   ├── projects.php
│   ├── project_details.php
│   ├── tasks.php
│   ├── files.php
│   ├── feedback.php
│   └── profile.php
│
├── api/
│   ├── tasks.php
│   ├── update_task_status.php
│   ├── add_comment.php
│   ├── upload_file.php
│   ├── search_users.php
│   ├── get_notifications.php
│   └── time_log.php
│
├── uploads/
│   ├── tasks/
│   ├── projects/
│   └── avatars/
│
├── vendor/
│
├── index.php
├── login.php
├── register.php
├── forgot_password.php
├── reset_password.php
├── logout.php
├── .env
├── .gitignore
├── composer.json
├── cron/
│   ├── send_reminders.php
│   └── update_task_status.php
└── sql/
└── database.sql

Database Schema

File: sql/database.sql

-- Create Database
CREATE DATABASE IF NOT EXISTS `task_management_system`;
USE `task_management_system`;
-- Users Table
CREATE TABLE `users` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_code` VARCHAR(20) UNIQUE NOT NULL,
`email` VARCHAR(100) UNIQUE NOT NULL,
`password` VARCHAR(255) NOT NULL,
`first_name` VARCHAR(50) NOT NULL,
`last_name` VARCHAR(50) NOT NULL,
`phone` VARCHAR(20),
`profile_picture` VARCHAR(255) DEFAULT 'default.png',
`role` ENUM('admin', 'manager', 'member', 'client') NOT NULL DEFAULT 'member',
`department` VARCHAR(100),
`position` VARCHAR(100),
`skills` TEXT,
`bio` TEXT,
`timezone` VARCHAR(50) DEFAULT 'UTC',
`email_notifications` BOOLEAN DEFAULT TRUE,
`push_notifications` BOOLEAN DEFAULT TRUE,
`status` ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
`email_verified` BOOLEAN DEFAULT FALSE,
`verification_token` VARCHAR(255),
`reset_token` VARCHAR(255),
`reset_expires` DATETIME,
`last_login` DATETIME,
`last_activity` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
INDEX `idx_email` (`email`),
INDEX `idx_role` (`role`),
INDEX `idx_status` (`status`),
FULLTEXT `idx_search` (`first_name`, `last_name`, `email`)
);
-- Teams Table
CREATE TABLE `teams` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`team_code` VARCHAR(20) UNIQUE NOT NULL,
`name` VARCHAR(100) NOT NULL,
`description` TEXT,
`department` VARCHAR(100),
`team_lead_id` INT(11),
`created_by` INT(11),
`status` ENUM('active', 'inactive') DEFAULT 'active',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`team_lead_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_name` (`name`)
);
-- Team Members Junction
CREATE TABLE `team_members` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`team_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`role_in_team` VARCHAR(50),
`joined_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_team_member` (`team_id`, `user_id`)
);
-- Projects Table
CREATE TABLE `projects` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_code` VARCHAR(20) UNIQUE NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`objective` TEXT,
`scope` TEXT,
`manager_id` INT(11) NOT NULL,
`team_id` INT(11),
`client_id` INT(11),
`status` ENUM('planning', 'active', 'on_hold', 'completed', 'cancelled') DEFAULT 'planning',
`priority` ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
`start_date` DATE,
`end_date` DATE,
`estimated_hours` DECIMAL(10,2),
`actual_hours` DECIMAL(10,2) DEFAULT 0,
`budget` DECIMAL(10,2),
`currency` VARCHAR(3) DEFAULT 'USD',
`progress` INT DEFAULT 0,
`color` VARCHAR(7) DEFAULT '#3498db',
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`manager_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`),
FOREIGN KEY (`client_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_status` (`status`),
INDEX `idx_dates` (`start_date`, `end_date`),
FULLTEXT `idx_search` (`name`, `description`)
);
-- Project Members Junction
CREATE TABLE `project_members` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`role_in_project` VARCHAR(50),
`assigned_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_project_member` (`project_id`, `user_id`)
);
-- Categories/Tags Table
CREATE TABLE `categories` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
`color` VARCHAR(7) DEFAULT '#6c757d',
`icon` VARCHAR(50),
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_name` (`name`)
);
-- Tasks Table
CREATE TABLE `tasks` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_code` VARCHAR(20) UNIQUE NOT NULL,
`title` VARCHAR(255) NOT NULL,
`description` TEXT,
`project_id` INT(11),
`category_id` INT(11),
`parent_task_id` INT(11),
`created_by` INT(11) NOT NULL,
`assigned_to` INT(11),
`status` ENUM('todo', 'in_progress', 'review', 'done', 'blocked') DEFAULT 'todo',
`priority` ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
`type` ENUM('task', 'bug', 'feature', 'improvement') DEFAULT 'task',
`estimated_hours` DECIMAL(10,2),
`actual_hours` DECIMAL(10,2) DEFAULT 0,
`start_date` DATE,
`due_date` DATE,
`completed_at` DATETIME,
`progress` INT DEFAULT 0,
`order_position` INT DEFAULT 0,
`tags` TEXT,
`attachments` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`category_id`) REFERENCES `categories`(`id`),
FOREIGN KEY (`parent_task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
FOREIGN KEY (`assigned_to`) REFERENCES `users`(`id`),
INDEX `idx_status` (`status`),
INDEX `idx_assigned` (`assigned_to`),
INDEX `idx_dates` (`start_date`, `due_date`),
FULLTEXT `idx_search` (`title`, `description`, `tags`)
);
-- Task Dependencies
CREATE TABLE `task_dependencies` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`depends_on` INT(11) NOT NULL,
`dependency_type` ENUM('finish_to_start', 'start_to_start', 'finish_to_finish', 'start_to_finish') DEFAULT 'finish_to_start',
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`depends_on`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_dependency` (`task_id`, `depends_on`)
);
-- Comments Table
CREATE TABLE `comments` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`parent_id` INT(11),
`comment` TEXT NOT NULL,
`mentions` TEXT,
`attachments` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY (`parent_id`) REFERENCES `comments`(`id`) ON DELETE CASCADE,
INDEX `idx_task` (`task_id`)
);
-- Time Tracking Table
CREATE TABLE `time_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11) NOT NULL,
`user_id` INT(11) NOT NULL,
`start_time` DATETIME NOT NULL,
`end_time` DATETIME,
`duration` INT, -- in minutes
`description` TEXT,
`billable` BOOLEAN DEFAULT TRUE,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
INDEX `idx_user_date` (`user_id`, `start_time`)
);
-- Files/Attachments Table
CREATE TABLE `files` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`task_id` INT(11),
`project_id` INT(11),
`user_id` INT(11) NOT NULL,
`filename` VARCHAR(255) NOT NULL,
`original_name` VARCHAR(255) NOT NULL,
`file_path` VARCHAR(500) NOT NULL,
`file_size` INT,
`file_type` VARCHAR(100),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`task_id`) REFERENCES `tasks`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`),
INDEX `idx_task` (`task_id`),
INDEX `idx_project` (`project_id`)
);
-- Notifications Table
CREATE TABLE `notifications` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`type` ENUM('task_assigned', 'task_updated', 'comment_added', 'mention', 'deadline_reminder', 'status_change', 'project_update') NOT NULL,
`title` VARCHAR(255) NOT NULL,
`message` TEXT NOT NULL,
`data` JSON,
`link` VARCHAR(500),
`is_read` BOOLEAN DEFAULT FALSE,
`read_at` DATETIME,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
INDEX `idx_user_read` (`user_id`, `is_read`),
INDEX `idx_created` (`created_at`)
);
-- Activity Logs Table
CREATE TABLE `activity_logs` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11),
`action` VARCHAR(100) NOT NULL,
`entity_type` ENUM('project', 'task', 'user', 'team', 'comment') NOT NULL,
`entity_id` INT(11),
`old_values` JSON,
`new_values` JSON,
`description` TEXT,
`ip_address` VARCHAR(45),
`user_agent` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE SET NULL,
INDEX `idx_entity` (`entity_type`, `entity_id`),
INDEX `idx_created` (`created_at`)
);
-- Milestones Table
CREATE TABLE `milestones` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`due_date` DATE NOT NULL,
`status` ENUM('pending', 'achieved', 'missed') DEFAULT 'pending',
`achieved_at` DATETIME,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`),
INDEX `idx_project` (`project_id`)
);
-- Budget Items Table
CREATE TABLE `budget_items` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`project_id` INT(11) NOT NULL,
`description` VARCHAR(255) NOT NULL,
`category` VARCHAR(100),
`estimated_amount` DECIMAL(10,2) NOT NULL,
`actual_amount` DECIMAL(10,2) DEFAULT 0,
`currency` VARCHAR(3) DEFAULT 'USD',
`date` DATE,
`notes` TEXT,
`created_by` INT(11),
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON DELETE CASCADE,
FOREIGN KEY (`created_by`) REFERENCES `users`(`id`)
);
-- User Settings Table
CREATE TABLE `user_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL,
`setting_key` VARCHAR(100) NOT NULL,
`setting_value` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON DELETE CASCADE,
UNIQUE KEY `unique_user_setting` (`user_id`, `setting_key`)
);
-- System Settings Table
CREATE TABLE `system_settings` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`setting_key` VARCHAR(100) UNIQUE NOT NULL,
`setting_value` TEXT,
`description` TEXT,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
);
-- Insert Default Categories
INSERT INTO `categories` (`name`, `color`, `icon`) VALUES
('Development', '#3498db', 'fa-code'),
('Design', '#9b59b6', 'fa-paint-brush'),
('Testing', '#e74c3c', 'fa-bug'),
('Documentation', '#2ecc71', 'fa-file-alt'),
('Meeting', '#f39c12', 'fa-users'),
('Research', '#1abc9c', 'fa-search'),
('Planning', '#34495e', 'fa-tasks'),
('Support', '#e67e22', 'fa-headset');
-- Insert System Settings
INSERT INTO `system_settings` (`setting_key`, `setting_value`, `description`) VALUES
('company_name', 'Task Management System', 'Company/Organization Name'),
('company_email', '[email protected]', 'System Email'),
('company_phone', '+1-555-123-4567', 'Contact Phone'),
('date_format', 'Y-m-d', 'Date format'),
('time_format', 'H:i', 'Time format'),
('timezone', 'UTC', 'Default timezone'),
('items_per_page', '20', 'Items per page in tables'),
('enable_notifications', '1', 'Enable email notifications'),
('default_task_view', 'list', 'Default task view (list/kanban/calendar)'),
('allow_attachments', '1', 'Allow file attachments'),
('max_file_size', '10', 'Maximum file size in MB'),
('allowed_file_types', 'jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt', 'Allowed file types'),
('session_timeout', '3600', 'Session timeout in seconds');
-- Insert Default Admin
INSERT INTO `users` (`user_code`, `email`, `password`, `first_name`, `last_name`, `role`, `email_verified`) 
VALUES ('ADMIN001', '[email protected]', '$2y$10$YourHashedPasswordHere', 'System', 'Administrator', 'admin', TRUE);

Core PHP Classes

Database Class

File: includes/Database.php

<?php
/**
* Database Class
* Handles all database connections and operations using PDO with singleton pattern
*/
class Database {
private static $instance = null;
private $connection;
private $statement;
private $host;
private $dbname;
private $username;
private $password;
/**
* Private constructor for singleton pattern
*/
private function __construct() {
$this->host = DB_HOST;
$this->dbname = DB_NAME;
$this->username = DB_USER;
$this->password = DB_PASS;
try {
$this->connection = new PDO(
"mysql:host={$this->host};dbname={$this->dbname};charset=utf8mb4",
$this->username,
$this->password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Database connection failed: " . $e->getMessage());
}
}
/**
* Get database instance (Singleton)
*/
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Prepare and execute query with parameters
*/
public function query($sql, $params = []) {
try {
$this->statement = $this->connection->prepare($sql);
$this->statement->execute($params);
return $this->statement;
} catch (PDOException $e) {
$this->logError($e->getMessage(), $sql, $params);
throw new Exception("Database query failed: " . $e->getMessage());
}
}
/**
* Get single row
*/
public function getRow($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetch();
}
/**
* Get multiple rows
*/
public function getRows($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchAll();
}
/**
* Get single value
*/
public function getValue($sql, $params = []) {
$result = $this->query($sql, $params);
return $result->fetchColumn();
}
/**
* Insert data and return last insert ID
*/
public function insert($table, $data) {
$columns = implode(', ', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";
$this->query($sql, $data);
return $this->connection->lastInsertId();
}
/**
* Update data
*/
public function update($table, $data, $where, $whereParams = []) {
$set = [];
foreach (array_keys($data) as $column) {
$set[] = "{$column} = :{$column}";
}
$sql = "UPDATE {$table} SET " . implode(', ', $set) . " WHERE {$where}";
$params = array_merge($data, $whereParams);
return $this->query($sql, $params)->rowCount();
}
/**
* Delete data
*/
public function delete($table, $where, $params = []) {
$sql = "DELETE FROM {$table} WHERE {$where}";
return $this->query($sql, $params)->rowCount();
}
/**
* Begin transaction
*/
public function beginTransaction() {
return $this->connection->beginTransaction();
}
/**
* Commit transaction
*/
public function commit() {
return $this->connection->commit();
}
/**
* Rollback transaction
*/
public function rollback() {
return $this->connection->rollBack();
}
/**
* Get last insert ID
*/
public function lastInsertId() {
return $this->connection->lastInsertId();
}
/**
* Log database errors
*/
private function logError($message, $sql, $params) {
$logFile = __DIR__ . '/../logs/database.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$logMessage = "[{$timestamp}] Error: {$message}\n";
$logMessage .= "SQL: {$sql}\n";
$logMessage .= "Params: " . json_encode($params) . "\n";
$logMessage .= "------------------------\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Prevent cloning of the instance
*/
private function __clone() {}
/**
* Prevent unserializing of the instance
*/
public function __wakeup() {}
}
?>

Configuration File

File: includes/config.php

<?php
/**
* Configuration File
* Loads environment variables and sets up constants
*/
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load environment variables from .env file
function loadEnv($path) {
if (!file_exists($path)) {
return false;
}
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
if (strpos(trim($line), '#') === 0) {
continue;
}
list($name, $value) = explode('=', $line, 2);
$name = trim($name);
$value = trim($value);
if (!array_key_exists($name, $_ENV)) {
$_ENV[$name] = $value;
putenv(sprintf('%s=%s', $name, $value));
}
}
return true;
}
// Load environment variables
loadEnv(__DIR__ . '/../.env');
// Database Configuration
define('DB_HOST', getenv('DB_HOST') ?: 'localhost');
define('DB_NAME', getenv('DB_NAME') ?: 'task_management_system');
define('DB_USER', getenv('DB_USER') ?: 'root');
define('DB_PASS', getenv('DB_PASS') ?: '');
// Application Configuration
define('APP_NAME', getenv('APP_NAME') ?: 'Task Management System');
define('APP_URL', getenv('APP_URL') ?: 'http://localhost/task-management-system');
define('APP_VERSION', getenv('APP_VERSION') ?: '1.0.0');
define('DEBUG_MODE', getenv('DEBUG_MODE') === 'true');
// Security Configuration
define('SESSION_TIMEOUT', getenv('SESSION_TIMEOUT') ?: 3600); // 1 hour
define('BCRYPT_ROUNDS', 12);
define('CSRF_TOKEN_NAME', 'csrf_token');
// Upload Configuration
define('UPLOAD_DIR', __DIR__ . '/../uploads/');
define('MAX_FILE_SIZE', getenv('MAX_FILE_SIZE') ?: 10 * 1024 * 1024); // 10MB
define('ALLOWED_EXTENSIONS', explode(',', getenv('ALLOWED_FILE_TYPES') ?: 'jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt'));
// Pagination
define('ITEMS_PER_PAGE', getenv('ITEMS_PER_PAGE') ?: 20);
// Date/Time Configuration
date_default_timezone_set(getenv('TIMEZONE') ?: 'UTC');
define('DATE_FORMAT', 'Y-m-d');
define('TIME_FORMAT', 'H:i');
define('DATETIME_FORMAT', 'Y-m-d H:i:s');
// Company Settings
define('COMPANY_NAME', getenv('COMPANY_NAME') ?: 'Task Management System');
define('COMPANY_EMAIL', getenv('COMPANY_EMAIL') ?: '[email protected]');
define('COMPANY_PHONE', getenv('COMPANY_PHONE') ?: '+1-555-123-4567');
// Notification Settings
define('ENABLE_NOTIFICATIONS', getenv('ENABLE_NOTIFICATIONS') === 'true');
// Error Reporting
if (DEBUG_MODE) {
error_reporting(E_ALL);
ini_set('display_errors', 1);
} else {
error_reporting(0);
ini_set('display_errors', 0);
}
// Include required files
require_once __DIR__ . '/Database.php';
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/Project.php';
require_once __DIR__ . '/Task.php';
require_once __DIR__ . '/Team.php';
require_once __DIR__ . '/Comment.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/ActivityLog.php';
require_once __DIR__ . '/Report.php';
// Initialize database connection
$db = Database::getInstance();
// Load system settings
$settings = $db->getRows("SELECT setting_key, setting_value FROM system_settings");
foreach ($settings as $setting) {
define(strtoupper($setting['setting_key']), $setting['setting_value']);
}
// Set timezone for MySQL
$db->query("SET time_zone = ?", [date('P')]);
?>

Helper Functions

File: includes/functions.php

<?php
/**
* Helper Functions
* Common utility functions used throughout the application
*/
/**
* Sanitize input data
*/
function sanitize($input) {
if (is_array($input)) {
return array_map('sanitize', $input);
}
return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8');
}
/**
* Generate CSRF token
*/
function generateCSRFToken() {
if (!isset($_SESSION[CSRF_TOKEN_NAME])) {
$_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
}
return $_SESSION[CSRF_TOKEN_NAME];
}
/**
* Verify CSRF token
*/
function verifyCSRFToken($token) {
if (!isset($_SESSION[CSRF_TOKEN_NAME]) || $token !== $_SESSION[CSRF_TOKEN_NAME]) {
return false;
}
return true;
}
/**
* Redirect to URL
*/
function redirect($url) {
header("Location: " . APP_URL . $url);
exit();
}
/**
* Format date
*/
function formatDate($date, $format = null) {
if ($format === null) {
$format = DATE_FORMAT;
}
if ($date instanceof DateTime) {
return $date->format($format);
}
if (empty($date) || $date == '0000-00-00') {
return 'N/A';
}
return date($format, strtotime($date));
}
/**
* Format datetime
*/
function formatDateTime($datetime, $format = null) {
if ($format === null) {
$format = DATETIME_FORMAT;
}
if (empty($datetime) || $datetime == '0000-00-00 00:00:00') {
return 'N/A';
}
return date($format, strtotime($datetime));
}
/**
* Format time
*/
function formatTime($time, $format = null) {
if ($format === null) {
$format = TIME_FORMAT;
}
return date($format, strtotime($time));
}
/**
* Get time ago string
*/
function timeAgo($datetime) {
if (empty($datetime)) {
return 'N/A';
}
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
return floor($diff / 60) . ' minutes ago';
} elseif ($diff < 86400) {
return floor($diff / 3600) . ' hours ago';
} elseif ($diff < 2592000) {
return floor($diff / 86400) . ' days ago';
} elseif ($diff < 31536000) {
return floor($diff / 2592000) . ' months ago';
} else {
return floor($diff / 31536000) . ' years ago';
}
}
/**
* Generate unique code
*/
function generateCode($prefix, $length = 8) {
return $prefix . strtoupper(substr(uniqid(), -$length));
}
/**
* Get status badge HTML
*/
function getStatusBadge($status) {
$badges = [
'todo' => 'secondary',
'in_progress' => 'primary',
'review' => 'warning',
'done' => 'success',
'blocked' => 'danger',
'planning' => 'info',
'active' => 'success',
'on_hold' => 'warning',
'completed' => 'success',
'cancelled' => 'danger',
'pending' => 'warning',
'achieved' => 'success',
'missed' => 'danger',
'low' => 'info',
'medium' => 'warning',
'high' => 'danger',
'urgent' => 'dark'
];
$statusText = str_replace('_', ' ', ucfirst($status));
$badgeClass = $badges[$status] ?? 'secondary';
return '<span class="badge bg-' . $badgeClass . '">' . $statusText . '</span>';
}
/**
* Get priority badge HTML
*/
function getPriorityBadge($priority) {
$badges = [
'low' => 'info',
'medium' => 'warning',
'high' => 'danger',
'urgent' => 'dark'
];
$priorityText = ucfirst($priority);
$badgeClass = $badges[$priority] ?? 'secondary';
return '<span class="badge bg-' . $badgeClass . '">' . $priorityText . '</span>';
}
/**
* Calculate progress percentage
*/
function calculateProgress($completed, $total) {
if ($total == 0) {
return 0;
}
return round(($completed / $total) * 100);
}
/**
* Format file size
*/
function formatFileSize($bytes, $precision = 2) {
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
/**
* Upload file
*/
function uploadFile($file, $targetDir, $allowedTypes = null) {
if ($allowedTypes === null) {
$allowedTypes = ALLOWED_EXTENSIONS;
}
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'error' => 'Upload failed with error code: ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'error' => 'File size exceeds limit of ' . (MAX_FILE_SIZE / 1024 / 1024) . 'MB'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if (!in_array($extension, $allowedTypes)) {
return ['success' => false, 'error' => 'File type not allowed. Allowed types: ' . implode(', ', $allowedTypes)];
}
// Generate unique filename
$filename = uniqid() . '_' . time() . '.' . $extension;
$targetPath = $targetDir . '/' . $filename;
// Create directory if not exists
if (!is_dir($targetDir)) {
mkdir($targetDir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return [
'success' => true,
'filename' => $filename,
'original_name' => $file['name'],
'path' => $targetPath,
'size' => $file['size'],
'type' => $file['type']
];
}
return ['success' => false, 'error' => 'Failed to move uploaded file'];
}
/**
* Delete file
*/
function deleteFile($path) {
if (file_exists($path)) {
return unlink($path);
}
return false;
}
/**
* Send email notification
*/
function sendEmail($to, $subject, $message) {
// For production, use PHPMailer with SMTP
// This is a simple mail function for development
$headers = [
'MIME-Version: 1.0',
'Content-type: text/html; charset=utf-8',
'From: ' . COMPANY_NAME . ' <' . COMPANY_EMAIL . '>',
'Reply-To: ' . COMPANY_EMAIL,
'X-Mailer: PHP/' . phpversion()
];
return mail($to, $subject, $message, implode("\r\n", $headers));
}
/**
* Log error
*/
function logError($message, $context = []) {
$logFile = __DIR__ . '/../logs/error.log';
$logDir = dirname($logFile);
if (!is_dir($logDir)) {
mkdir($logDir, 0755, true);
}
$timestamp = date('Y-m-d H:i:s');
$contextStr = !empty($context) ? ' ' . json_encode($context) : '';
$logMessage = "[{$timestamp}] {$message}{$contextStr}\n";
file_put_contents($logFile, $logMessage, FILE_APPEND);
}
/**
* Get user IP address
*/
function getUserIP() {
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
return $_SERVER['HTTP_CLIENT_IP'];
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
return $_SERVER['HTTP_X_FORWARDED_FOR'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}
/**
* Generate random string
*/
function generateRandomString($length = 32) {
return bin2hex(random_bytes($length / 2));
}
/**
* Validate email
*/
function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* Get user avatar URL
*/
function getAvatarUrl($avatar) {
if ($avatar && file_exists(UPLOAD_DIR . 'avatars/' . $avatar)) {
return APP_URL . '/uploads/avatars/' . $avatar;
}
return APP_URL . '/assets/images/default-avatar.png';
}
/**
* Get user full name
*/
function getUserFullName($user) {
return $user['first_name'] . ' ' . $user['last_name'];
}
/**
* Get initials from name
*/
function getInitials($name) {
$words = explode(' ', $name);
$initials = '';
foreach ($words as $word) {
$initials .= strtoupper(substr($word, 0, 1));
}
return substr($initials, 0, 2);
}
/**
* Generate pagination
*/
function paginate($currentPage, $totalPages, $url) {
if ($totalPages <= 1) {
return '';
}
$html = '<nav aria-label="Page navigation"><ul class="pagination">';
// Previous button
if ($currentPage > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage - 1) . '">Previous</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Previous</span></li>';
}
// Page numbers
for ($i = 1; $i <= $totalPages; $i++) {
if ($i == $currentPage) {
$html .= '<li class="page-item active"><span class="page-link">' . $i . '</span></li>';
} else {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . $i . '">' . $i . '</a></li>';
}
}
// Next button
if ($currentPage < $totalPages) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '?page=' . ($currentPage + 1) . '">Next</a></li>';
} else {
$html .= '<li class="page-item disabled"><span class="page-link">Next</span></li>';
}
$html .= '</ul></nav>';
return $html;
}
/**
* Check if user has permission
*/
function hasPermission($user, $requiredRole) {
$roles = [
'admin' => 4,
'manager' => 3,
'member' => 2,
'client' => 1
];
$userLevel = $roles[$user['role']] ?? 0;
$requiredLevel = $roles[$requiredRole] ?? 0;
return $userLevel >= $requiredLevel;
}
/**
* Get task status options
*/
function getTaskStatusOptions() {
return [
'todo' => 'To Do',
'in_progress' => 'In Progress',
'review' => 'Review',
'done' => 'Done',
'blocked' => 'Blocked'
];
}
/**
* Get priority options
*/
function getPriorityOptions() {
return [
'low' => 'Low',
'medium' => 'Medium',
'high' => 'High',
'urgent' => 'Urgent'
];
}
/**
* Get task type options
*/
function getTaskTypeOptions() {
return [
'task' => 'Task',
'bug' => 'Bug',
'feature' => 'Feature',
'improvement' => 'Improvement'
];
}
/**
* Calculate days remaining until deadline
*/
function daysRemaining($dueDate) {
if (empty($dueDate)) {
return null;
}
$now = new DateTime();
$due = new DateTime($dueDate);
$interval = $now->diff($due);
if ($due < $now) {
return -$interval->days;
}
return $interval->days;
}
/**
* Get overdue status
*/
function isOverdue($dueDate) {
if (empty($dueDate)) {
return false;
}
$now = new DateTime();
$due = new DateTime($dueDate);
return $due < $now;
}
/**
* Format duration (minutes to hours and minutes)
*/
function formatDuration($minutes) {
if ($minutes < 60) {
return $minutes . ' min';
}
$hours = floor($minutes / 60);
$mins = $minutes % 60;
if ($mins == 0) {
return $hours . ' hr' . ($hours > 1 ? 's' : '');
}
return $hours . ' hr ' . $mins . ' min';
}
/**
* Get contrast color for background
*/
function getContrastColor($hexColor) {
// Convert hex to RGB
$hexColor = str_replace('#', '', $hexColor);
$r = hexdec(substr($hexColor, 0, 2));
$g = hexdec(substr($hexColor, 2, 2));
$b = hexdec(substr($hexColor, 4, 2));
// Calculate luminance
$luminance = (0.299 * $r + 0.587 * $g + 0.114 * $b) / 255;
return $luminance > 0.5 ? '#000000' : '#ffffff';
}
?>

Authentication Class

File: includes/auth.php

<?php
/**
* Authentication Class
* Handles user authentication, registration, and session management
*/
class Auth {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Register new user
*/
public function register($data) {
try {
// Check if email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Email already registered'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate user code
$userCode = generateCode('USR');
// Generate verification token
$verificationToken = generateRandomString();
// Prepare user data
$userData = [
'user_code' => $userCode,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? 'member',
'verification_token' => $verificationToken
];
// Insert user
$newUserId = $this->db->insert('users', $userData);
if ($newUserId) {
// Send verification email
$this->sendVerificationEmail($data['email'], $verificationToken);
// Log activity
$this->logActivity($newUserId, 'user_registered', 'user', $newUserId, null, null, 'New user registered');
return [
'success' => true,
'user_id' => $newUserId,
'message' => 'Registration successful. Please check your email to verify your account.'
];
}
return ['success' => false, 'error' => 'Registration failed'];
} catch (Exception $e) {
logError('Registration error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Registration failed: ' . $e->getMessage()];
}
}
/**
* Login user
*/
public function login($email, $password, $remember = false) {
try {
// Get user
$user = $this->db->getRow(
"SELECT * FROM users WHERE email = ? AND status = 'active'",
[$email]
);
if (!$user) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if email verified
if (!$user['email_verified']) {
return ['success' => false, 'error' => 'Please verify your email before logging in'];
}
// Verify password
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'error' => 'Invalid email or password'];
}
// Check if password needs rehash
if (password_needs_rehash($user['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS])) {
$newHash = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $newHash], 'id = :id', ['id' => $user['id']]);
}
// Set session
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_code'] = $user['user_code'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['user_name'] = $user['first_name'] . ' ' . $user['last_name'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['logged_in'] = true;
$_SESSION['login_time'] = time();
// Update last login
$this->db->update(
'users',
['last_login' => date('Y-m-d H:i:s'), 'last_activity' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'user_login', 'user', $user['id'], null, null, 'User logged in');
// Set remember me cookie
if ($remember) {
$this->setRememberMe($user['id']);
}
return ['success' => true, 'user' => $user];
} catch (Exception $e) {
logError('Login error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Login failed'];
}
}
/**
* Set remember me cookie
*/
private function setRememberMe($userId) {
$token = generateRandomString(64);
$expires = time() + (86400 * 30); // 30 days
// Store token in database (you would need a remember_tokens table)
// For now, just set cookie
setcookie('remember_token', $token, $expires, '/', '', false, true);
}
/**
* Logout user
*/
public function logout() {
if (isset($_SESSION['user_id'])) {
// Log activity
$this->logActivity($_SESSION['user_id'], 'user_logout', 'user', $_SESSION['user_id'], null, null, 'User logged out');
}
// Clear session
$_SESSION = array();
// Clear session cookie
if (ini_get('session.use_cookies')) {
$params = session_get_cookie_params();
setcookie(
session_name(),
'',
time() - 42000,
$params['path'],
$params['domain'],
$params['secure'],
$params['httponly']
);
}
// Clear remember me cookie
setcookie('remember_token', '', time() - 3600, '/');
// Destroy session
session_destroy();
}
/**
* Check if user is logged in
*/
public function isLoggedIn() {
return isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true;
}
/**
* Get current user
*/
public function getCurrentUser() {
if (!$this->isLoggedIn()) {
return null;
}
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$_SESSION['user_id']]
);
}
/**
* Check if user has role
*/
public function hasRole($role) {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === $role;
}
/**
* Check if user has any of the roles
*/
public function hasAnyRole($roles) {
return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], $roles);
}
/**
* Require login
*/
public function requireLogin() {
if (!$this->isLoggedIn()) {
$_SESSION['error'] = 'Please login to access this page';
redirect('/login.php');
}
}
/**
* Require role
*/
public function requireRole($role) {
$this->requireLogin();
if (is_array($role)) {
if (!in_array($_SESSION['user_role'], $role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
redirect('/dashboard.php');
}
} else {
if (!$this->hasRole($role)) {
$_SESSION['error'] = 'You do not have permission to access this page';
// Redirect based on role
if ($this->hasRole('admin')) {
redirect('/admin/dashboard.php');
} elseif ($this->hasRole('manager')) {
redirect('/manager/dashboard.php');
} elseif ($this->hasRole('member')) {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
}
}
}
/**
* Verify email
*/
public function verifyEmail($token) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE verification_token = ?",
[$token]
);
if ($user) {
$this->db->update(
'users',
['email_verified' => true, 'verification_token' => null],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'email_verified', 'user', $user['id'], null, null, 'Email verified');
return true;
}
return false;
}
/**
* Send verification email
*/
private function sendVerificationEmail($email, $token) {
$subject = "Verify your email - " . APP_NAME;
$message = "<h1>Email Verification</h1>";
$message .= "<p>Click the link below to verify your email address:</p>";
$message .= "<p><a href='" . APP_URL . "/verify.php?token=" . $token . "'>Verify Email</a></p>";
$message .= "<p>If you didn't create an account, you can ignore this email.</p>";
return sendEmail($email, $subject, $message);
}
/**
* Forgot password
*/
public function forgotPassword($email) {
$user = $this->db->getRow(
"SELECT id, first_name FROM users WHERE email = ?",
[$email]
);
if ($user) {
$token = generateRandomString();
$expires = date('Y-m-d H:i:s', strtotime('+1 hour'));
$this->db->update(
'users',
['reset_token' => $token, 'reset_expires' => $expires],
'id = :id',
['id' => $user['id']]
);
// Send reset email
$subject = "Password Reset - " . APP_NAME;
$message = "<h1>Password Reset Request</h1>";
$message .= "<p>Hello " . $user['first_name'] . ",</p>";
$message .= "<p>Click the link below to reset your password (valid for 1 hour):</p>";
$message .= "<p><a href='" . APP_URL . "/reset_password.php?token=" . $token . "'>Reset Password</a></p>";
$message .= "<p>If you didn't request this, you can ignore this email.</p>";
return sendEmail($email, $subject, $message);
}
return false;
}
/**
* Reset password
*/
public function resetPassword($token, $password) {
$user = $this->db->getRow(
"SELECT id FROM users WHERE reset_token = ? AND reset_expires > NOW()",
[$token]
);
if ($user) {
$hashedPassword = password_hash($password, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update(
'users',
['password' => $hashedPassword, 'reset_token' => null, 'reset_expires' => null],
'id = :id',
['id' => $user['id']]
);
// Log activity
$this->logActivity($user['id'], 'password_reset', 'user', $user['id'], null, null, 'Password reset');
return true;
}
return false;
}
/**
* Update user profile
*/
public function updateProfile($userId, $data) {
try {
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'bio' => $data['bio'] ?? null,
'timezone' => $data['timezone'] ?? 'UTC',
'email_notifications' => isset($data['email_notifications']) ? 1 : 0,
'push_notifications' => isset($data['push_notifications']) ? 1 : 0
];
// Handle profile picture upload
if (isset($_FILES['profile_picture']) && $_FILES['profile_picture']['error'] == 0) {
$uploadDir = UPLOAD_DIR . 'avatars/';
$result = uploadFile($_FILES['profile_picture'], $uploadDir);
if ($result['success']) {
// Delete old picture
$oldUser = $this->db->getRow("SELECT profile_picture FROM users WHERE id = ?", [$userId]);
if ($oldUser && $oldUser['profile_picture'] != 'default.png') {
deleteFile(UPLOAD_DIR . 'avatars/' . $oldUser['profile_picture']);
}
$updateData['profile_picture'] = $result['filename'];
}
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
// Log activity
$this->logActivity($userId, 'profile_updated', 'user', $userId, null, null, 'Profile updated');
return ['success' => true, 'message' => 'Profile updated successfully'];
} catch (Exception $e) {
logError('Profile update error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to update profile'];
}
}
/**
* Change password
*/
public function changePassword($userId, $currentPassword, $newPassword) {
$user = $this->db->getRow("SELECT password FROM users WHERE id = ?", [$userId]);
if (!password_verify($currentPassword, $user['password'])) {
return ['success' => false, 'error' => 'Current password is incorrect'];
}
$hashedPassword = password_hash($newPassword, PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
$this->db->update('users', ['password' => $hashedPassword], 'id = :id', ['id' => $userId]);
// Log activity
$this->logActivity($userId, 'password_changed', 'user', $userId, null, null, 'Password changed');
return ['success' => true, 'message' => 'Password changed successfully'];
}
/**
* Log activity
*/
private function logActivity($userId, $action, $entityType, $entityId, $oldValues, $newValues, $description) {
$log = new ActivityLog();
$log->create($userId, $action, $entityType, $entityId, $oldValues, $newValues, $description);
}
/**
* Update last activity
*/
public function updateLastActivity() {
if ($this->isLoggedIn()) {
$this->db->update(
'users',
['last_activity' => date('Y-m-d H:i:s')],
'id = :id',
['id' => $_SESSION['user_id']]
);
}
}
}
// Initialize Auth
$auth = new Auth();
// Update last activity
$auth->updateLastActivity();
?>

User Class

File: includes/User.php

<?php
/**
* User Class
* Handles all user-related operations
*/
class User {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get user by ID
*/
public function getUser($userId) {
return $this->db->getRow(
"SELECT * FROM users WHERE id = ?",
[$userId]
);
}
/**
* Get user by email
*/
public function getUserByEmail($email) {
return $this->db->getRow(
"SELECT * FROM users WHERE email = ?",
[$email]
);
}
/**
* Get all users with filters
*/
public function getUsers($filters = []) {
$sql = "SELECT * FROM users WHERE 1=1";
$params = [];
if (!empty($filters['role'])) {
$sql .= " AND role = :role";
$params['role'] = $filters['role'];
}
if (!empty($filters['status'])) {
$sql .= " AND status = :status";
$params['status'] = $filters['status'];
}
if (!empty($filters['search'])) {
$sql .= " AND (first_name LIKE :search OR last_name LIKE :search OR email LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
if (!empty($filters['team_id'])) {
$sql .= " AND id IN (SELECT user_id FROM team_members WHERE team_id = :team_id)";
$params['team_id'] = $filters['team_id'];
}
if (!empty($filters['project_id'])) {
$sql .= " AND id IN (SELECT user_id FROM project_members WHERE project_id = :project_id)";
$params['project_id'] = $filters['project_id'];
}
$sql .= " ORDER BY first_name ASC, last_name ASC";
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Get user count
*/
public function getUserCount($filters = []) {
$sql = "SELECT COUNT(*) as count FROM users WHERE 1=1";
$params = [];
if (!empty($filters['role'])) {
$sql .= " AND role = :role";
$params['role'] = $filters['role'];
}
if (!empty($filters['status'])) {
$sql .= " AND status = :status";
$params['status'] = $filters['status'];
}
$result = $this->db->getRow($sql, $params);
return $result['count'];
}
/**
* Create new user (admin only)
*/
public function createUser($data) {
try {
// Check if email already exists
$existing = $this->db->getRow(
"SELECT id FROM users WHERE email = ?",
[$data['email']]
);
if ($existing) {
return ['success' => false, 'error' => 'Email already exists'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
// Generate user code
$userCode = generateCode('USR');
$userData = [
'user_code' => $userCode,
'email' => $data['email'],
'password' => $hashedPassword,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? 'member',
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'email_verified' => 1, // Admin created users are automatically verified
'status' => $data['status'] ?? 'active'
];
$userId = $this->db->insert('users', $userData);
if ($userId) {
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_created',
'user',
$userId,
null,
$userData,
'User created by admin'
);
return ['success' => true, 'user_id' => $userId, 'message' => 'User created successfully'];
}
return ['success' => false, 'error' => 'Failed to create user'];
} catch (Exception $e) {
logError('Create user error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to create user'];
}
}
/**
* Update user (admin only)
*/
public function updateUser($userId, $data) {
try {
$user = $this->getUser($userId);
if (!$user) {
return ['success' => false, 'error' => 'User not found'];
}
$updateData = [
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'phone' => $data['phone'] ?? null,
'role' => $data['role'] ?? $user['role'],
'department' => $data['department'] ?? null,
'position' => $data['position'] ?? null,
'status' => $data['status'] ?? $user['status']
];
// Update password if provided
if (!empty($data['password'])) {
$updateData['password'] = password_hash($data['password'], PASSWORD_BCRYPT, ['cost' => BCRYPT_ROUNDS]);
}
$this->db->update('users', $updateData, 'id = :id', ['id' => $userId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_updated',
'user',
$userId,
$user,
$updateData,
'User updated by admin'
);
return ['success' => true, 'message' => 'User updated successfully'];
} catch (Exception $e) {
logError('Update user error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to update user'];
}
}
/**
* Delete user (admin only)
*/
public function deleteUser($userId) {
try {
$user = $this->getUser($userId);
if (!$user) {
return ['success' => false, 'error' => 'User not found'];
}
// Check if user has active tasks
$activeTasks = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status != 'done'",
[$userId]
);
if ($activeTasks > 0) {
return ['success' => false, 'error' => 'Cannot delete user with active tasks'];
}
// Soft delete - update status
$this->db->update('users', ['status' => 'inactive'], 'id = :id', ['id' => $userId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'user_deleted',
'user',
$userId,
$user,
null,
'User deactivated by admin'
);
return ['success' => true, 'message' => 'User deactivated successfully'];
} catch (Exception $e) {
logError('Delete user error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete user'];
}
}
/**
* Get user teams
*/
public function getUserTeams($userId) {
return $this->db->getRows(
"SELECT t.*, tm.role_in_team
FROM teams t
JOIN team_members tm ON t.id = tm.team_id
WHERE tm.user_id = ? AND t.status = 'active'",
[$userId]
);
}
/**
* Get user projects
*/
public function getUserProjects($userId) {
return $this->db->getRows(
"SELECT p.*, pm.role_in_project
FROM projects p
JOIN project_members pm ON p.id = pm.project_id
WHERE pm.user_id = ? AND p.status NOT IN ('completed', 'cancelled')",
[$userId]
);
}
/**
* Get user tasks
*/
public function getUserTasks($userId, $status = null) {
$sql = "SELECT t.*, p.name as project_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
WHERE t.assigned_to = :user_id";
$params = ['user_id' => $userId];
if ($status) {
$sql .= " AND t.status = :status";
$params['status'] = $status;
}
$sql .= " ORDER BY t.due_date ASC, t.priority DESC";
return $this->db->getRows($sql, $params);
}
/**
* Get user dashboard stats
*/
public function getUserStats($userId) {
$stats = [];
// Total tasks assigned
$stats['total_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ?",
[$userId]
);
// Tasks by status
$stats['todo_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'todo'",
[$userId]
);
$stats['in_progress_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'in_progress'",
[$userId]
);
$stats['review_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'review'",
[$userId]
);
$stats['done_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE assigned_to = ? AND status = 'done'",
[$userId]
);
// Overdue tasks
$stats['overdue_tasks'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks 
WHERE assigned_to = ? 
AND status NOT IN ('done', 'cancelled')
AND due_date < CURDATE()",
[$userId]
);
// Upcoming deadlines (next 7 days)
$stats['upcoming_deadlines'] = $this->db->getValue(
"SELECT COUNT(*) FROM tasks 
WHERE assigned_to = ? 
AND status NOT IN ('done', 'cancelled')
AND due_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 7 DAY)",
[$userId]
);
// Total projects
$stats['total_projects'] = $this->db->getValue(
"SELECT COUNT(DISTINCT project_id) FROM project_members WHERE user_id = ?",
[$userId]
);
// Total time logged (current month)
$stats['time_logged_month'] = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs 
WHERE user_id = ? 
AND MONTH(start_time) = MONTH(CURDATE())
AND YEAR(start_time) = YEAR(CURDATE())",
[$userId]
) ?: 0;
return $stats;
}
/**
* Search users for mentions
*/
public function searchUsers($query, $projectId = null, $limit = 10) {
$sql = "SELECT id, first_name, last_name, email, profile_picture 
FROM users 
WHERE status = 'active' 
AND (first_name LIKE :query OR last_name LIKE :query OR email LIKE :query)";
$params = ['query' => '%' . $query . '%'];
if ($projectId) {
$sql .= " AND id IN (SELECT user_id FROM project_members WHERE project_id = :project_id)";
$params['project_id'] = $projectId;
}
$sql .= " LIMIT :limit";
$params['limit'] = $limit;
return $this->db->getRows($sql, $params);
}
/**
* Get user notifications
*/
public function getNotifications($userId, $unreadOnly = false, $limit = null) {
$sql = "SELECT * FROM notifications WHERE user_id = :user_id";
$params = ['user_id' => $userId];
if ($unreadOnly) {
$sql .= " AND is_read = 0";
}
$sql .= " ORDER BY created_at DESC";
if ($limit) {
$sql .= " LIMIT :limit";
$params['limit'] = $limit;
}
return $this->db->getRows($sql, $params);
}
/**
* Mark notification as read
*/
public function markNotificationRead($notificationId, $userId) {
return $this->db->update(
'notifications',
['is_read' => true, 'read_at' => date('Y-m-d H:i:s')],
'id = :id AND user_id = :user_id',
['id' => $notificationId, 'user_id' => $userId]
);
}
/**
* Mark all notifications as read
*/
public function markAllNotificationsRead($userId) {
return $this->db->update(
'notifications',
['is_read' => true, 'read_at' => date('Y-m-d H:i:s')],
'user_id = :user_id AND is_read = 0',
['user_id' => $userId]
);
}
}
?>

Task Class

File: includes/Task.php

<?php
/**
* Task Class
* Handles all task-related operations
*/
class Task {
private $db;
/**
* Constructor
*/
public function __construct() {
$this->db = Database::getInstance();
}
/**
* Get task by ID
*/
public function getTask($taskId) {
$task = $this->db->getRow(
"SELECT t.*, 
p.name as project_name,
c.name as category_name, c.color as category_color,
creator.first_name as creator_first_name, creator.last_name as creator_last_name,
assignee.first_name as assignee_first_name, assignee.last_name as assignee_last_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
LEFT JOIN categories c ON t.category_id = c.id
LEFT JOIN users creator ON t.created_by = creator.id
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE t.id = ? OR t.task_code = ?",
[$taskId, $taskId]
);
if ($task) {
// Get tags as array
$task['tags_array'] = $task['tags'] ? explode(',', $task['tags']) : [];
// Get attachments as array
$task['attachments_array'] = $task['attachments'] ? json_decode($task['attachments'], true) : [];
// Get subtasks
$task['subtasks'] = $this->getSubtasks($task['id']);
// Get dependencies
$task['dependencies'] = $this->getDependencies($task['id']);
// Get comments count
$task['comments_count'] = $this->db->getValue(
"SELECT COUNT(*) FROM comments WHERE task_id = ?",
[$task['id']]
);
// Get time logged
$task['time_logged'] = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs WHERE task_id = ?",
[$task['id']]
) ?: 0;
}
return $task;
}
/**
* Get tasks with filters
*/
public function getTasks($filters = []) {
$sql = "SELECT t.*, 
p.name as project_name,
c.name as category_name, c.color as category_color,
assignee.first_name as assignee_first_name, 
assignee.last_name as assignee_last_name
FROM tasks t
LEFT JOIN projects p ON t.project_id = p.id
LEFT JOIN categories c ON t.category_id = c.id
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE 1=1";
$params = [];
if (!empty($filters['project_id'])) {
$sql .= " AND t.project_id = :project_id";
$params['project_id'] = $filters['project_id'];
}
if (!empty($filters['assigned_to'])) {
$sql .= " AND t.assigned_to = :assigned_to";
$params['assigned_to'] = $filters['assigned_to'];
}
if (!empty($filters['created_by'])) {
$sql .= " AND t.created_by = :created_by";
$params['created_by'] = $filters['created_by'];
}
if (!empty($filters['status'])) {
if (is_array($filters['status'])) {
$placeholders = implode(',', array_fill(0, count($filters['status']), '?'));
$sql .= " AND t.status IN ($placeholders)";
$params = array_merge($params, $filters['status']);
} else {
$sql .= " AND t.status = :status";
$params['status'] = $filters['status'];
}
}
if (!empty($filters['priority'])) {
$sql .= " AND t.priority = :priority";
$params['priority'] = $filters['priority'];
}
if (!empty($filters['category_id'])) {
$sql .= " AND t.category_id = :category_id";
$params['category_id'] = $filters['category_id'];
}
if (!empty($filters['due_date_from'])) {
$sql .= " AND t.due_date >= :due_date_from";
$params['due_date_from'] = $filters['due_date_from'];
}
if (!empty($filters['due_date_to'])) {
$sql .= " AND t.due_date <= :due_date_to";
$params['due_date_to'] = $filters['due_date_to'];
}
if (!empty($filters['overdue'])) {
$sql .= " AND t.due_date < CURDATE() AND t.status NOT IN ('done', 'cancelled')";
}
if (!empty($filters['search'])) {
$sql .= " AND (t.title LIKE :search OR t.description LIKE :search OR t.tags LIKE :search)";
$params['search'] = '%' . $filters['search'] . '%';
}
if (!empty($filters['tags'])) {
$tags = explode(',', $filters['tags']);
foreach ($tags as $index => $tag) {
$sql .= " AND t.tags LIKE :tag_$index";
$params['tag_' . $index] = '%' . trim($tag) . '%';
}
}
$sql .= " ORDER BY " . ($filters['order_by'] ?? 't.due_date ASC, t.priority DESC');
// Pagination
$page = $filters['page'] ?? 1;
$limit = $filters['limit'] ?? ITEMS_PER_PAGE;
$offset = ($page - 1) * $limit;
$sql .= " LIMIT :limit OFFSET :offset";
$params['limit'] = $limit;
$params['offset'] = $offset;
return $this->db->getRows($sql, $params);
}
/**
* Create new task
*/
public function createTask($data) {
try {
$this->db->beginTransaction();
// Generate task code
$taskCode = generateCode('TSK');
// Handle tags
$tags = '';
if (!empty($data['tags'])) {
if (is_array($data['tags'])) {
$tags = implode(',', $data['tags']);
} else {
$tags = $data['tags'];
}
}
$taskData = [
'task_code' => $taskCode,
'title' => $data['title'],
'description' => $data['description'] ?? null,
'project_id' => $data['project_id'] ?? null,
'category_id' => $data['category_id'] ?? null,
'parent_task_id' => $data['parent_task_id'] ?? null,
'created_by' => $_SESSION['user_id'],
'assigned_to' => $data['assigned_to'] ?? null,
'status' => $data['status'] ?? 'todo',
'priority' => $data['priority'] ?? 'medium',
'type' => $data['type'] ?? 'task',
'estimated_hours' => $data['estimated_hours'] ?? null,
'start_date' => $data['start_date'] ?? null,
'due_date' => $data['due_date'] ?? null,
'tags' => $tags
];
$taskId = $this->db->insert('tasks', $taskData);
if ($taskId) {
// Handle dependencies
if (!empty($data['dependencies'])) {
foreach ($data['dependencies'] as $depId) {
$this->addDependency($taskId, $depId);
}
}
// Add to project members if not already
if (!empty($data['project_id']) && !empty($data['assigned_to'])) {
$this->ensureProjectMember($data['project_id'], $data['assigned_to']);
}
// Create notification for assignee
if (!empty($data['assigned_to'])) {
$this->sendAssignmentNotification($taskId, $data['assigned_to']);
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_created',
'task',
$taskId,
null,
$taskData,
'Task created'
);
}
$this->db->commit();
return ['success' => true, 'task_id' => $taskId, 'task_code' => $taskCode, 'message' => 'Task created successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Create task error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to create task'];
}
}
/**
* Update task
*/
public function updateTask($taskId, $data) {
try {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldAssignedTo = $task['assigned_to'];
// Handle tags
$tags = $task['tags'];
if (isset($data['tags'])) {
if (is_array($data['tags'])) {
$tags = implode(',', $data['tags']);
} else {
$tags = $data['tags'];
}
}
$updateData = [
'title' => $data['title'] ?? $task['title'],
'description' => $data['description'] ?? $task['description'],
'project_id' => $data['project_id'] ?? $task['project_id'],
'category_id' => $data['category_id'] ?? $task['category_id'],
'assigned_to' => $data['assigned_to'] ?? $task['assigned_to'],
'priority' => $data['priority'] ?? $task['priority'],
'type' => $data['type'] ?? $task['type'],
'estimated_hours' => $data['estimated_hours'] ?? $task['estimated_hours'],
'start_date' => $data['start_date'] ?? $task['start_date'],
'due_date' => $data['due_date'] ?? $task['due_date'],
'tags' => $tags
];
// Handle status change
if (isset($data['status']) && $data['status'] != $task['status']) {
$updateData['status'] = $data['status'];
if ($data['status'] == 'done' && $task['status'] != 'done') {
$updateData['completed_at'] = date('Y-m-d H:i:s');
$updateData['progress'] = 100;
}
}
$this->db->update('tasks', $updateData, 'id = :id', ['id' => $taskId]);
// Send notification if assignee changed
if ($updateData['assigned_to'] != $oldAssignedTo && !empty($updateData['assigned_to'])) {
$this->sendAssignmentNotification($taskId, $updateData['assigned_to']);
}
// Add to project members if not already
if (!empty($updateData['project_id']) && !empty($updateData['assigned_to'])) {
$this->ensureProjectMember($updateData['project_id'], $updateData['assigned_to']);
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_updated',
'task',
$taskId,
$task,
$updateData,
'Task updated'
);
return ['success' => true, 'message' => 'Task updated successfully'];
} catch (Exception $e) {
logError('Update task error: ' . $e->getMessage(), $data);
return ['success' => false, 'error' => 'Failed to update task'];
}
}
/**
* Update task status
*/
public function updateStatus($taskId, $status) {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldStatus = $task['status'];
$updateData = ['status' => $status];
if ($status == 'done' && $oldStatus != 'done') {
$updateData['completed_at'] = date('Y-m-d H:i:s');
$updateData['progress'] = 100;
}
$this->db->update('tasks', $updateData, 'id = :id', ['id' => $taskId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_status_changed',
'task',
$taskId,
['status' => $oldStatus],
['status' => $status],
'Task status changed from ' . $oldStatus . ' to ' . $status
);
return ['success' => true, 'message' => 'Task status updated'];
}
/**
* Update task progress
*/
public function updateProgress($taskId, $progress) {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
$oldProgress = $task['progress'];
$this->db->update(
'tasks',
['progress' => $progress],
'id = :id',
['id' => $taskId]
);
// If progress is 100%, mark as done
if ($progress == 100 && $task['status'] != 'done') {
$this->updateStatus($taskId, 'done');
}
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_progress_updated',
'task',
$taskId,
['progress' => $oldProgress],
['progress' => $progress],
'Task progress updated'
);
return ['success' => true, 'message' => 'Progress updated'];
}
/**
* Delete task
*/
public function deleteTask($taskId) {
try {
$task = $this->getTask($taskId);
if (!$task) {
return ['success' => false, 'error' => 'Task not found'];
}
// Check if task has subtasks
$subtasks = $this->db->getValue(
"SELECT COUNT(*) FROM tasks WHERE parent_task_id = ?",
[$taskId]
);
if ($subtasks > 0) {
return ['success' => false, 'error' => 'Cannot delete task with subtasks'];
}
$this->db->beginTransaction();
// Delete dependencies
$this->db->delete('task_dependencies', 'task_id = ? OR depends_on = ?', [$taskId, $taskId]);
// Delete comments
$this->db->delete('comments', 'task_id = ?', [$taskId]);
// Delete time logs
$this->db->delete('time_logs', 'task_id = ?', [$taskId]);
// Delete task
$this->db->delete('tasks', 'id = ?', [$taskId]);
// Log activity
$activityLog = new ActivityLog();
$activityLog->create(
$_SESSION['user_id'],
'task_deleted',
'task',
$taskId,
$task,
null,
'Task deleted'
);
$this->db->commit();
return ['success' => true, 'message' => 'Task deleted successfully'];
} catch (Exception $e) {
$this->db->rollback();
logError('Delete task error: ' . $e->getMessage());
return ['success' => false, 'error' => 'Failed to delete task'];
}
}
/**
* Get subtasks
*/
public function getSubtasks($parentTaskId) {
return $this->db->getRows(
"SELECT t.*, assignee.first_name, assignee.last_name
FROM tasks t
LEFT JOIN users assignee ON t.assigned_to = assignee.id
WHERE t.parent_task_id = ?
ORDER BY t.order_position ASC",
[$parentTaskId]
);
}
/**
* Add dependency
*/
public function addDependency($taskId, $dependsOn) {
// Check for circular dependency
if ($this->wouldCreateCircularDependency($taskId, $dependsOn)) {
return false;
}
return $this->db->insert('task_dependencies', [
'task_id' => $taskId,
'depends_on' => $dependsOn
]);
}
/**
* Check for circular dependency
*/
private function wouldCreateCircularDependency($taskId, $dependsOn) {
if ($taskId == $dependsOn) {
return true;
}
// Check if depends_on already depends on taskId
$deps = $this->db->getRows(
"SELECT task_id FROM task_dependencies WHERE depends_on = ?",
[$dependsOn]
);
foreach ($deps as $dep) {
if ($this->wouldCreateCircularDependency($taskId, $dep['task_id'])) {
return true;
}
}
return false;
}
/**
* Get dependencies
*/
public function getDependencies($taskId) {
return $this->db->getRows(
"SELECT t.*, td.dependency_type
FROM tasks t
JOIN task_dependencies td ON t.id = td.depends_on
WHERE td.task_id = ?",
[$taskId]
);
}
/**
* Get dependent tasks
*/
public function getDependentTasks($taskId) {
return $this->db->getRows(
"SELECT t.*, td.dependency_type
FROM tasks t
JOIN task_dependencies td ON t.id = td.task_id
WHERE td.depends_on = ?",
[$taskId]
);
}
/**
* Ensure user is project member
*/
private function ensureProjectMember($projectId, $userId) {
$exists = $this->db->getValue(
"SELECT id FROM project_members WHERE project_id = ? AND user_id = ?",
[$projectId, $userId]
);
if (!$exists) {
$this->db->insert('project_members', [
'project_id' => $projectId,
'user_id' => $userId
]);
}
}
/**
* Send assignment notification
*/
private function sendAssignmentNotification($taskId, $userId) {
$task = $this->getTask($taskId);
$notification = new Notification();
$notification->create(
$userId,
'task_assigned',
'New Task Assigned',
"You have been assigned to task: " . $task['title'],
['task_id' => $taskId],
'/member/task_details.php?id=' . $taskId
);
}
/**
* Get task statistics
*/
public function getTaskStats($projectId = null) {
$sql = "SELECT 
COUNT(*) as total_tasks,
SUM(CASE WHEN status = 'todo' THEN 1 ELSE 0 END) as todo,
SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress,
SUM(CASE WHEN status = 'review' THEN 1 ELSE 0 END) as review,
SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done,
SUM(CASE WHEN status = 'blocked' THEN 1 ELSE 0 END) as blocked,
SUM(CASE WHEN due_date < CURDATE() AND status NOT IN ('done', 'cancelled') THEN 1 ELSE 0 END) as overdue,
SUM(CASE WHEN priority = 'urgent' THEN 1 ELSE 0 END) as urgent
FROM tasks";
$params = [];
if ($projectId) {
$sql .= " WHERE project_id = :project_id";
$params['project_id'] = $projectId;
}
return $this->db->getRow($sql, $params);
}
/**
* Get tasks for Gantt chart
*/
public function getGanttData($projectId) {
$tasks = $this->db->getRows(
"SELECT t.id, t.title, t.start_date, t.due_date as end_date, t.progress,
t.duration, t.dependencies
FROM tasks t
WHERE t.project_id = ? AND t.start_date IS NOT NULL AND t.due_date IS NOT NULL
ORDER BY t.start_date ASC",
[$projectId]
);
$ganttData = [];
foreach ($tasks as $task) {
$deps = $this->getDependencies($task['id']);
$depIds = [];
foreach ($deps as $dep) {
$depIds[] = $dep['id'];
}
$ganttData[] = [
'id' => $task['id'],
'name' => $task['title'],
'start' => $task['start_date'],
'end' => $task['end_date'],
'progress' => $task['progress'],
'dependencies' => implode(',', $depIds)
];
}
return $ganttData;
}
/**
* Log time on task
*/
public function logTime($taskId, $userId, $startTime, $endTime, $description = null) {
$start = new DateTime($startTime);
$end = new DateTime($endTime);
$interval = $start->diff($end);
$duration = ($interval->days * 24 * 60) + ($interval->h * 60) + $interval->i;
$logId = $this->db->insert('time_logs', [
'task_id' => $taskId,
'user_id' => $userId,
'start_time' => $startTime,
'end_time' => $endTime,
'duration' => $duration,
'description' => $description
]);
if ($logId) {
// Update task actual hours
$totalTime = $this->db->getValue(
"SELECT SUM(duration) FROM time_logs WHERE task_id = ?",
[$taskId]
);
$this->db->update(
'tasks',
['actual_hours' => $totalTime / 60],
'id = :id',
['id' => $taskId]
);
return ['success' => true, 'log_id' => $logId];
}
return ['success' => false];
}
}
?>

Frontend Pages

Login Page

File: login.php

<?php
require_once 'includes/config.php';
// Redirect if already logged in
if ($auth->isLoggedIn()) {
$role = $_SESSION['user_role'];
if ($role == 'admin') {
redirect('/admin/dashboard.php');
} elseif ($role == 'manager') {
redirect('/manager/dashboard.php');
} elseif ($role == 'member') {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
}
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = sanitize($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
$remember = isset($_POST['remember']);
if (empty($email) || empty($password)) {
$error = 'Please enter email and password';
} else {
$result = $auth->login($email, $password, $remember);
if ($result['success']) {
$user = $result['user'];
// Redirect based on role
if ($user['role'] === 'admin') {
redirect('/admin/dashboard.php');
} elseif ($user['role'] === 'manager') {
redirect('/manager/dashboard.php');
} elseif ($user['role'] === 'member') {
redirect('/member/dashboard.php');
} else {
redirect('/client/dashboard.php');
}
} else {
$error = $result['error'];
}
}
}
// Check for session messages
if (isset($_SESSION['success'])) {
$success = $_SESSION['success'];
unset($_SESSION['success']);
}
if (isset($_SESSION['error'])) {
$error = $_SESSION['error'];
unset($_SESSION['error']);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body class="bg-light">
<div class="container">
<div class="row justify-content-center align-items-center min-vh-100">
<div class="col-md-6 col-lg-5">
<div class="card shadow-lg border-0 rounded-lg">
<div class="card-header bg-primary text-white text-center py-4">
<h3 class="mb-0">
<i class="fas fa-tasks me-2"></i>
<?php echo APP_NAME; ?>
</h3>
<p class="mb-0 text-white-50">Manage your tasks efficiently</p>
</div>
<div class="card-body p-5">
<h4 class="text-center mb-4">Welcome Back!</h4>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-circle me-2"></i>
<?php echo $error; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show">
<i class="fas fa-check-circle me-2"></i>
<?php echo $success; ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php endif; ?>
<form method="POST" action="" onsubmit="return validateLogin()">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<div class="mb-4">
<label for="email" class="form-label">
<i class="fas fa-envelope me-2"></i>Email Address
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-envelope"></i></span>
<input type="email" class="form-control" id="email" name="email" 
placeholder="Enter your email" value="<?php echo htmlspecialchars($_POST['email'] ?? ''); ?>" required>
</div>
</div>
<div class="mb-4">
<label for="password" class="form-label">
<i class="fas fa-lock me-2"></i>Password
</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-lock"></i></span>
<input type="password" class="form-control" id="password" name="password" 
placeholder="Enter your password" required>
<button class="btn btn-outline-secondary" type="button" onclick="togglePassword()">
<i class="fas fa-eye" id="togglePasswordIcon"></i>
</button>
</div>
</div>
<div class="mb-4 form-check">
<input type="checkbox" class="form-check-input" id="remember" name="remember">
<label class="form-check-label" for="remember">Remember me</label>
<a href="forgot_password.php" class="float-end text-decoration-none">
Forgot Password?
</a>
</div>
<button type="submit" class="btn btn-primary w-100 py-2 mb-3">
<i class="fas fa-sign-in-alt me-2"></i>Login
</button>
</form>
<div class="text-center">
<p class="mb-0">
Don't have an account? 
<a href="register.php" class="text-decoration-none">Register here</a>
</p>
</div>
<hr class="my-4">
<div class="text-center">
<a href="index.php" class="text-decoration-none">
<i class="fas fa-arrow-left me-1"></i>Back to Home
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
function togglePassword() {
const password = document.getElementById('password');
const icon = document.getElementById('togglePasswordIcon');
if (password.type === 'password') {
password.type = 'text';
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
password.type = 'password';
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
function validateLogin() {
const email = document.getElementById('email').value.trim();
const password = document.getElementById('password').value;
if (email === '') {
alert('Please enter your email');
return false;
}
if (password === '') {
alert('Please enter your password');
return false;
}
return true;
}
</script>
</body>
</html>

Member Dashboard

File: member/dashboard.php

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
// Require login and member role
$auth->requireRole(['member', 'manager', 'admin']);
// Get current user
$user = $auth->getCurrentUser();
// Initialize classes
$taskObj = new Task();
$userObj = new User();
// Get user stats
$stats = $userObj->getUserStats($user['id']);
// Get assigned tasks
$tasks = $taskObj->getTasks([
'assigned_to' => $user['id'],
'status' => ['todo', 'in_progress', 'review'],
'limit' => 10
]);
// Get overdue tasks
$overdueTasks = $taskObj->getTasks([
'assigned_to' => $user['id'],
'overdue' => true,
'limit' => 5
]);
// Get recent activities
$activityLog = new ActivityLog();
$recentActivities = $activityLog->getUserActivities($user['id'], 10);
// Get notifications
$notifications = $userObj->getNotifications($user['id'], true, 5);
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - <?php echo APP_NAME; ?></title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Chart.js -->
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<!-- Custom CSS -->
<link rel="stylesheet" href="../assets/css/dashboard.css">
</head>
<body>
<div class="d-flex">
<!-- Sidebar -->
<div class="sidebar bg-dark text-white">
<div class="sidebar-header p-4">
<a href="dashboard.php" class="text-white text-decoration-none">
<i class="fas fa-tasks fa-2x mb-3"></i>
<h5 class="mb-0"><?php echo APP_NAME; ?></h5>
<small>Task Management</small>
</a>
</div>
<div class="user-info p-4 border-top border-secondary">
<div class="d-flex align-items-center">
<div class="avatar bg-primary rounded-circle d-flex align-items-center justify-content-center me-3" 
style="width: 50px; height: 50px; font-size: 1.2rem; font-weight: bold;">
<?php echo getInitials($user['first_name'] . ' ' . $user['last_name']); ?>
</div>
<div>
<h6 class="mb-0"><?php echo htmlspecialchars($user['first_name'] . ' ' . $user['last_name']); ?></h6>
<small class="text-secondary"><?php echo ucfirst($user['role']); ?></small>
</div>
</div>
</div>
<nav class="nav flex-column p-3">
<a href="dashboard.php" class="nav-link text-white active">
<i class="fas fa-tachometer-alt me-3"></i>Dashboard
</a>
<a href="my_tasks.php" class="nav-link text-white">
<i class="fas fa-tasks me-3"></i>My Tasks
<?php if ($stats['todo_tasks'] > 0): ?>
<span class="badge bg-primary ms-auto"><?php echo $stats['todo_tasks']; ?></span>
<?php endif; ?>
</a>
<a href="calendar.php" class="nav-link text-white">
<i class="fas fa-calendar-alt me-3"></i>Calendar
</a>
<a href="time_tracking.php" class="nav-link text-white">
<i class="fas fa-clock me-3"></i>Time Tracking
</a>
<a href="files.php" class="nav-link text-white">
<i class="fas fa-file-alt me-3"></i>Files
</a>
<a href="notifications.php" class="nav-link text-white">
<i class="fas fa-bell me-3"></i>Notifications
<?php if (count($notifications) > 0): ?>
<span class="badge bg-danger ms-auto"><?php echo count($notifications); ?></span>
<?php endif; ?>
</a>
<a href="profile.php" class="nav-link text-white">
<i class="fas fa-user-circle me-3"></i>Profile
</a>
<a href="../logout.php" class="nav-link text-white mt-4">
<i class="fas fa-sign-out-alt me-3"></i>Logout
</a>
</nav>
</div>
<!-- Main Content -->
<div class="main-content flex-grow-1 bg-light">
<header class="bg-white shadow-sm p-3">
<div class="d-flex justify-content-between align-items-center">
<h5 class="mb-0">
<i class="fas fa-tachometer-alt me-2 text-primary"></i>
Dashboard
</h5>
<div class="d-flex align-items-center">
<!-- Notifications Dropdown -->
<div class="dropdown me-3">
<button class="btn btn-light position-relative" type="button" data-bs-toggle="dropdown">
<i class="fas fa-bell"></i>
<?php if (count($notifications) > 0): ?>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?php echo count($notifications); ?>
</span>
<?php endif; ?>
</button>
<div class="dropdown-menu dropdown-menu-end notification-dropdown" style="width: 300px;">
<?php if (count($notifications) > 0): ?>
<?php foreach ($notifications as $note): ?>
<a class="dropdown-item" href="<?php echo $note['link']; ?>">
<small class="text-muted d-block"><?php echo timeAgo($note['created_at']); ?></small>
<strong><?php echo htmlspecialchars($note['title']); ?></strong>
<p class="mb-0 small text-muted"><?php echo htmlspecialchars($note['message']); ?></p>
</a>
<div class="dropdown-divider"></div>
<?php endforeach; ?>
<a class="dropdown-item text-center" href="notifications.php">
View all notifications
</a>
<?php else: ?>
<span class="dropdown-item text-center text-muted">
No new notifications
</span>
<?php endif; ?>
</div>
</div>
<!-- Date -->
<span class="text-muted">
<i class="fas fa-calendar me-1"></i>
<?php echo date('F j, Y'); ?>
</span>
</div>
</div>
</header>
<div class="p-4">
<!-- Welcome Banner -->
<div class="row mb-4">
<div class="col-12">
<div class="card bg-primary text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<h4>Welcome back, <?php echo htmlspecialchars($user['first_name']); ?>!</h4>
<p class="mb-0">
Here's your task summary for today. You have 
<strong><?php echo $stats['todo_tasks']; ?> tasks</strong> to do and 
<strong><?php echo $stats['in_progress_tasks']; ?> in progress</strong>.
</p>
</div>
<div class="col-md-4 text-md-end">
<a href="my_tasks.php" class="btn btn-light">
<i class="fas fa-tasks me-2"></i>View All Tasks
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Stats Cards -->
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">To Do</h6>
<h3 class="mb-0 text-primary"><?php echo $stats['todo_tasks']; ?></h3>
</div>
<div class="icon-circle bg-primary bg-opacity-10 text-primary">
<i class="fas fa-clipboard-list"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">In Progress</h6>
<h3 class="mb-0 text-warning"><?php echo $stats['in_progress_tasks']; ?></h3>
</div>
<div class="icon-circle bg-warning bg-opacity-10 text-warning">
<i class="fas fa-spinner"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">Review</h6>
<h3 class="mb-0 text-info"><?php echo $stats['review_tasks']; ?></h3>
</div>
<div class="icon-circle bg-info bg-opacity-10 text-info">
<i class="fas fa-eye"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card border-0 shadow-sm h-100">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center">
<div>
<h6 class="text-muted mb-2">Completed</h6>
<h3 class="mb-0 text-success"><?php echo $stats['done_tasks']; ?></h3>
</div>
<div class="icon-circle bg-success bg-opacity-10 text-success">
<i class="fas fa-check-circle"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Overdue Tasks Alert -->
<?php if ($stats['overdue_tasks'] > 0): ?>
<div class="col-12 mb-4">
<div class="alert alert-danger alert-dismissible fade show">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Overdue Tasks!</strong> You have <?php echo $stats['overdue_tasks']; ?> overdue task(s).
<a href="my_tasks.php?filter=overdue" class="alert-link">View now</a>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
<?php endif; ?>
<!-- Upcoming Deadlines -->
<?php if ($stats['upcoming_deadlines'] > 0): ?>
<div class="col-12 mb-4">
<div class="alert alert-warning alert-dismissible fade show">
<i class="fas fa-clock me-2"></i>
<strong>Upcoming Deadlines!</strong> You have <?php echo $stats['upcoming_deadlines']; ?> task(s) due in the next 7 days.
<a href="my_tasks.php?filter=upcoming" class="alert-link">View now</a>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
</div>
<?php endif; ?>
</div>
<div class="row">
<!-- My Tasks -->
<div class="col-lg-8 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0">
<i class="fas fa-tasks me-2 text-primary"></i>
My Tasks
</h6>
<a href="my_tasks.php" class="btn btn-sm btn-outline-primary">
View All
</a>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<?php if (count($tasks) > 0): ?>
<?php foreach ($tasks as $task): ?>
<div class="list-group-item">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="d-flex align-items-center">
<div class="me-3">
<?php if ($task['priority'] == 'urgent'): ?>
<span class="badge bg-danger">Urgent</span>
<?php elseif ($task['priority'] == 'high'): ?>
<span class="badge bg-warning">High</span>
<?php elseif ($task['priority'] == 'medium'): ?>
<span class="badge bg-info">Medium</span>
<?php else: ?>
<span class="badge bg-secondary">Low</span>
<?php endif; ?>
</div>
<div>
<h6 class="mb-1">
<a href="task_details.php?id=<?php echo $task['id']; ?>" class="text-decoration-none">
<?php echo htmlspecialchars($task['title']); ?>
</a>
</h6>
<small class="text-muted">
<?php if ($task['project_name']): ?>
<i class="fas fa-folder me-1"></i><?php echo htmlspecialchars($task['project_name']); ?> •
<?php endif; ?>
<i class="fas fa-calendar me-1"></i>
Due: <?php echo $task['due_date'] ? formatDate($task['due_date']) : 'No deadline'; ?>
<?php if (isOverdue($task['due_date']) && $task['status'] != 'done'): ?>
<span class="badge bg-danger ms-2">Overdue</span>
<?php endif; ?>
</small>
</div>
</div>
</div>
<div>
<?php echo getStatusBadge($task['status']); ?>
</div>
</div>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-center py-5">
<i class="fas fa-check-circle fa-3x text-success mb-3"></i>
<p class="text-muted">No tasks assigned yet. Great job!</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
<!-- Recent Activity -->
<div class="col-lg-4 mb-4">
<div class="card border-0 shadow-sm h-100">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-history me-2 text-primary"></i>
Recent Activity
</h6>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
<?php if (count($recentActivities) > 0): ?>
<?php foreach ($recentActivities as $activity): ?>
<div class="list-group-item">
<small class="text-muted d-block"><?php echo timeAgo($activity['created_at']); ?></small>
<p class="mb-0 small"><?php echo htmlspecialchars($activity['description']); ?></p>
</div>
<?php endforeach; ?>
<?php else: ?>
<div class="text-center py-4">
<p class="text-muted small mb-0">No recent activity</p>
</div>
<?php endif; ?>
</div>
</div>
</div>
</div>
</div>
<!-- Task Progress Chart -->
<div class="row">
<div class="col-md-6 mb-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-chart-pie me-2 text-primary"></i>
Task Status Distribution
</h6>
</div>
<div class="card-body">
<canvas id="taskChart" style="height: 300px;"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0">
<i class="fas fa-clock me-2 text-primary"></i>
Time Logged This Month
</h6>
</div>
<div class="card-body text-center">
<h1 class="display-3 text-primary"><?php echo formatDuration($stats['time_logged_month']); ?></h1>
<p class="text-muted">Total time logged in <?php echo date('F Y'); ?></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Task Status Chart
const ctx = document.getElementById('taskChart').getContext('2d');
new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['To Do', 'In Progress', 'Review', 'Done'],
datasets: [{
data: [
<?php echo $stats['todo_tasks']; ?>,
<?php echo $stats['in_progress_tasks']; ?>,
<?php echo $stats['review_tasks']; ?>,
<?php echo $stats['done_tasks']; ?>
],
backgroundColor: ['#3498db', '#f39c12', '#9b59b6', '#2ecc71'],
borderWidth: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
</script>
<style>
.sidebar {
width: 280px;
min-height: 100vh;
}
.main-content {
margin-left: 280px;
}
.nav-link {
color: rgba(255,255,255,0.8) !important;
padding: 12px 20px;
border-radius: 5px;
margin: 2px 10px;
transition: all 0.3s;
}
.nav-link:hover,
.nav-link.active {
background: rgba(255,255,255,0.1) !important;
color: white !important;
}
.nav-link i {
width: 20px;
}
.icon-circle {
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
}
.avatar {
width: 50px;
height: 50px;
}
.notification-dropdown {
max-height: 400px;
overflow-y: auto;
}
@media (max-width: 768px) {
.sidebar {
transform: translateX(-100%);
position: fixed;
z-index: 1000;
}
.main-content {
margin-left: 0;
}
}
</style>
</body>
</html>

Environment Configuration

File: .env

# Database Configuration
DB_HOST=localhost
DB_NAME=task_management_system
DB_USER=root
DB_PASS=
# Application Configuration
APP_NAME=Task Management System
APP_URL=http://localhost/task-management-system
APP_VERSION=1.0.0
DEBUG_MODE=true
# Security
SESSION_TIMEOUT=3600
BCRYPT_ROUNDS=12
# Upload Configuration
MAX_FILE_SIZE=10485760  # 10MB in bytes
ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,txt
# Pagination
ITEMS_PER_PAGE=20
# Date/Time
TIMEZONE=UTC
DATE_FORMAT=Y-m-d
TIME_FORMAT=H:i
# Company Settings
COMPANY_NAME=Task Management System
[email protected]
COMPANY_PHONE=+1-555-123-4567
# Notification Settings
ENABLE_NOTIFICATIONS=true

File: .gitignore

# Environment variables
.env
# Dependencies
/vendor/
node_modules/
# IDE files
.vscode/
.idea/
*.sublime-*
# OS files
.DS_Store
Thumbs.db
# Logs
/logs/
*.log
# Uploads
/uploads/
!/uploads/.gitkeep
# Composer
composer.lock
# Temp files
*.tmp
*.temp

File: composer.json

{
"name": "task-management-system/application",
"description": "Complete Task Management System for Teams",
"type": "project",
"require": {
"php": ">=7.4",
"ext-pdo": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-curl": "*",
"ext-gd": "*",
"phpmailer/phpmailer": "^6.8",
"tecnickcom/tcpdf": "^6.6"
},
"require-dev": {
"phpunit/phpunit": "^9.0"
},
"autoload": {
"psr-4": {
"TaskSystem\\": "src/"
}
},
"scripts": {
"test": "phpunit tests",
"post-install-cmd": [
"chmod -R 755 uploads/",
"chmod -R 755 logs/"
]
}
}

How to Use the Project (Step-by-Step Guide)

Prerequisites

  1. Web Server: XAMPP, WAMP, MAMP, or any PHP-enabled server (PHP 7.4+)
  2. Database: MySQL 5.7+ or MariaDB
  3. Composer: For dependency management
  4. Browser: Modern web browser (Chrome, Firefox, Edge, etc.)

Installation Steps

Step 1: Set Up Local Server

  1. Download and install XAMPP from https://www.apachefriends.org/
  2. Launch XAMPP Control Panel
  3. Start Apache and MySQL services

Step 2: Install Composer Dependencies

  1. Download and install Composer from https://getcomposer.org/
  2. Navigate to your project directory in terminal
  3. Run: composer install

Step 3: Create Project Folder

  1. Navigate to C:\xampp\htdocs\ (Windows) or /Applications/XAMPP/htdocs/ (Mac)
  2. Create a new folder named task-management-system
  3. Create all the folders and files as shown in the Project File Structure

Step 4: Set Up Database

  1. Open browser and go to http://localhost/phpmyadmin
  2. Click on "New" to create a new database
  3. Name the database task_management_system and select utf8_general_ci
  4. Click on "Import" tab
  5. Click "Choose File" and select the database.sql file from the sql folder
  6. Click "Go" to import the database structure and sample data

Step 5: Configure Environment

  1. Rename .env.example to .env in the project root
  2. Update database credentials if different from default:
   DB_HOST=localhost
DB_NAME=task_management_system
DB_USER=root
DB_PASS=
  1. Update application URL:
   APP_URL=http://localhost/task-management-system
  1. Configure email settings if using email notifications

Step 6: Set Folder Permissions

Create the following folders and ensure they are writable:

  • uploads/avatars/
  • uploads/tasks/
  • uploads/projects/
  • logs/

On Windows, right-click folders → Properties → Security → give Write permission to Users
On Mac/Linux, run: chmod -R 755 uploads/ logs/

Step 7: Create Admin Password Hash

  1. Go to http://localhost/task-management-system/register.php
  2. Register a test user (e.g., email: [email protected], password: Admin@123)
  3. Open phpMyAdmin, go to the users table
  4. Find the user and change role to 'admin'
  5. Set email_verified to 1

Step 8: Test the Installation

  1. Open browser and go to http://localhost/task-management-system/
  2. You should see the login page
  3. Test different user types: Admin Login:
  • Email: [email protected]
  • Password: Admin@123 Manager Registration:
  • Register a new user
  • Admin can promote to manager in admin panel Member Registration:
  • Register as a new member
  • Login and view dashboard

System Walkthrough

For Members:

  1. Dashboard - View task summary, assigned tasks, and recent activity
  2. My Tasks - View all assigned tasks with filters
  3. Task Details - View task information, add comments, update status
  4. Calendar - View tasks on calendar by due date
  5. Time Tracking - Log time spent on tasks
  6. Files - Access shared files
  7. Notifications - View system notifications
  8. Profile - Update personal information and preferences

For Managers:

  1. Project Dashboard - Overview of all managed projects
  2. Project Management - Create and manage projects
  3. Task Management - Create, assign, and track tasks
  4. Team Management - Manage team members and assignments
  5. Gantt Chart - Visual timeline of project tasks
  6. Reports - Generate project status reports
  7. Budget Tracking - Monitor project budgets

For Admins:

  1. System Dashboard - Overview of system statistics
  2. User Management - Create, edit, and manage users
  3. Team Management - Create teams and assign members
  4. Project Oversight - View all projects
  5. System Settings - Configure global parameters
  6. Audit Logs - Track all system activities
  7. Backup - Manage database backups

Key Features Explained

Task Management

  1. Create tasks with title, description, priority, and due date
  2. Assign tasks to team members
  3. Set task dependencies (e.g., Task B depends on Task A)
  4. Track task status (To Do, In Progress, Review, Done)
  5. Add comments and mentions
  6. Attach files to tasks
  7. Log time spent on tasks
  8. View task history and changes

Project Management

  1. Create projects with timelines and budgets
  2. Set project milestones
  3. Track project progress with Gantt charts
  4. Manage project team members
  5. Monitor project budgets and expenses
  6. Generate project reports

Collaboration Features

  1. Comment on tasks with @mentions
  2. Share files within tasks
  3. Receive notifications for assignments and updates
  4. View activity logs
  5. Team calendar for deadline visibility

Cron Jobs Setup

Create the following cron jobs for automated tasks:

# Send deadline reminders (run daily at 8 AM)
0 8 * * * php /path/to/project/cron/send_reminders.php
# Update task status for overdue tasks (run daily at midnight)
0 0 * * * php /path/to/project/cron/update_task_status.php
# Clean up old logs (run weekly on Sunday at 1 AM)
0 1 * * 0 php /path/to/project/cron/clean_logs.php

Troubleshooting

Common Issues and Solutions

  1. Database Connection Error
  • Check if MySQL is running
  • Verify database credentials in .env
  • Ensure database task_management_system exists
  1. 404 Page Not Found
  • Check file paths and folder structure
  • Verify APP_URL in .env
  • Ensure .htaccess is properly configured (if using Apache)
  1. Email Not Sending
  • Configure SMTP settings in PHPMailer
  • Check spam folder
  • Verify PHP mail() function is enabled
  1. File Upload Issues
  • Check folder permissions
  • Verify MAX_FILE_SIZE in .env
  • Check allowed file types
  1. Session/Login Issues
  • Clear browser cookies and cache
  • Check SESSION_TIMEOUT in .env
  • Verify session save path is writable
  1. Task Dependency Issues
  • Check for circular dependencies
  • Verify task IDs exist
  • Ensure proper dependency type

Conclusion

The Task Management System is a comprehensive, feature-rich platform for managing tasks, projects, and team collaboration. With its intuitive interface, powerful features, and flexible role-based access control, it provides everything needed for efficient task and project management.

This application demonstrates:

  • Secure user authentication with role-based access
  • Complete CRUD operations for tasks, projects, and users
  • Task dependencies and subtask management
  • Gantt charts for project timeline visualization
  • Comment system with mentions
  • File attachments for tasks
  • Time tracking for tasks
  • Notification system for updates and reminders
  • Activity logging for audit trails
  • Responsive design for all devices
  • Modular code structure following OOP principles
  • Database design with proper relationships
  • Error handling and logging
  • Security best practices throughout

The system is built to be scalable, allowing easy addition of new features such as:

  • API integration with external tools
  • Mobile apps for iOS and Android
  • Advanced reporting and analytics
  • Resource allocation and workload management
  • Integration with calendar applications
  • Export to various formats (PDF, Excel)
  • Multi-language support

With proper deployment, regular backups, and security updates, this system can serve as a reliable platform for teams and organizations of any size to manage their tasks and projects efficiently.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper