Skip to main content

Project Management System with PHP and MySQL

In our previous Python tutorial, we have explained how to develop School Management System with PHP & MySQL. In this tutorial, we will explain how to develop Project Management System with PHP and MySQL.

The Project Management System (PMS) is a web application used for project planning, resource allocation, organize, tracking, estimation etc. Depending on the requirement, the project management systems are developed to manage project components such as cost control, budget management, quality management, estimation, planning, assignment and much more.

So if you’re are developer and looking for solution to develop your own web based project management system, then you’re here at the right place. In this tutorial, you will learn how to develop your own project management system with PHP and MySQL.

Here we will develop a simple project management system with live demo to manage clients, projects and tasks with milestone, costs and hours. You can easily customize this projects according to your requirements.

Also, read:

So let’s implement Project Management System with PHP and MySQL. The major files are:

  • index.php
  • clients.php
  • projects.php
  • tasks.php
  • User.php: A class contains users methods.
  • Clients.php: A class contains clients methods.
  • Project.php: A class contains project methods.
  • Tasks.php: A class contains task methods.

Step1: Create MySQL Database Table

First we will create MySQL database tables for our project management system. The major tables are following.

We will create pm_users table to store users details.

CREATE TABLE `pm_users` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `email` varchar(255) DEFAULT NULL,
  `first_name` varchar(255) DEFAULT NULL,
  `last_name` varchar(255) DEFAULT NULL,
  `role` enum('admin','manager','employee') DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We will create pm_clients table to store clients details.

CREATE TABLE `pm_clients` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `website` varchar(255) DEFAULT NULL,
  `industry` varchar(255) DEFAULT NULL,
  `revenue` varchar(255) DEFAULT NULL,
  `description` varchar(512) DEFAULT '',
  `phone` varchar(255) DEFAULT NULL,
  `street1` varchar(255) DEFAULT NULL,
  `street2` varchar(255) DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `state` varchar(255) DEFAULT NULL,
  `zip` int(11) DEFAULT NULL,
  `country` varchar(255) DEFAULT NULL,
  `notes` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We will create pm_projects table to store project details.

CREATE TABLE `pm_projects` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `project_name` varchar(255) DEFAULT NULL,
  `project_manager_id` int(11) unsigned NOT NULL,
  `start_date` date DEFAULT NULL,
  `hourly_rate` float DEFAULT NULL,
  `budget` float DEFAULT NULL,
  `active` int(11) unsigned NOT NULL,
  `status_id` int(11) unsigned NOT NULL,
  `client_id` int(11) unsigned DEFAULT NULL,
  `total_hours` float DEFAULT NULL,
  `labor_costs` float DEFAULT NULL,
  `material_cost` float DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We will create pm_tasks table to store tasks details.

CREATE TABLE `pm_tasks` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `task_name` varchar(255) DEFAULT NULL,
  `instruction` varchar(255) DEFAULT NULL,
  `status` int(11) unsigned NOT NULL,
  `total_hours` float DEFAULT NULL,
  `milestone` int(11) unsigned DEFAULT NULL,
  `project_id` int(11) unsigned NOT NULL,
  `employee_id` int(11) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We will create pms_hours table to store hours details.

CREATE TABLE `pms_hours` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `date` date DEFAULT NULL,
  `time` float DEFAULT NULL,
  `work_completed` varchar(255) DEFAULT NULL,
  `task_id` int(11) unsigned NOT NULL,
  `project_id` int(11) unsigned NOT NULL,
  `employee_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Step2: Create User Login Form

We will design user login form to allow login to access system. The manager will manage the clients and projects section while employee will manage tasks section to add work hours.

<div class="panel panel-info">
	<div class="panel-heading" style="background:#00796B;color:white;">
		<div class="panel-title">Log In</div>                        
	</div> 
	<div style="padding-top:30px" class="panel-body" >
		<?php if ($loginMessage != '') { ?>
			<div id="login-alert" class="alert alert-danger col-sm-12"><?php echo $loginMessage; ?></div>                            
		<?php } ?>
		<form id="loginform" class="form-horizontal" role="form" method="POST" action="">                                    
			<div style="margin-bottom: 25px" class="input-group">
				<span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span>
				<input type="text" class="form-control" id="email" name="email" value="<?php if(!empty($_POST["email"])) { echo $_POST["email"]; } ?>" placeholder="email" style="background:white;" required>                                        
			</div>                                
			<div style="margin-bottom: 25px" class="input-group">
				<span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span>
				<input type="password" class="form-control" id="password" name="password" value="<?php if(!empty($_POST["password"])) { echo $_POST["password"]; } ?>" placeholder="password" required>
			</div>					
			<div style="margin-top:10px" class="form-group">                               
				<div class="col-sm-12 controls">
				  <input type="submit" name="login" value="Login" class="btn btn-info">						  
				</div>						
			</div>				
				
		</form>   
	</div>                     
</div>  

We will implement user login functionality using login() method on login form submit. We will display the system section according to user role.

$loginMessage = '';
if(!empty($_POST["login"]) && !empty($_POST["email"]) && !empty($_POST["password"])) {	
	$user->email = $_POST["email"];
	$user->password = $_POST["password"];	
	if($user->login()) {
		if($_SESSION["role"] == 'manager') {
			header("Location: clients.php");
		} else if($_SESSION["role"] == 'employee') {
			header("Location: tasks.php");
		}
	} else {
		$loginMessage = 'Invalid login! Please try again.';
	}
} else {
	$loginMessage = 'Fill all fields.';
}

In class User.php, we will implement login() method and store user login details to SESSION variables.

public function login(){
	if($this->email && $this->password) {
		$sqlQuery = "
			SELECT * FROM ".$this->userTable." 
			WHERE email = ? AND password = ?";			
		$stmt = $this->conn->prepare($sqlQuery);
		$password = md5($this->password);
		$stmt->bind_param("ss", $this->email, $password);	
		$stmt->execute();
		$result = $stmt->get_result();
		if($result->num_rows > 0){
			$user = $result->fetch_assoc();
			$_SESSION["userid"] = $user['id'];
			$_SESSION["role"] = $user['role'];
			$_SESSION["name"] = $user['first_name']." ".$user['last_name'];					
			return 1;		
		} else {
			return 0;		
		}			
	} else {
		return 0;
	}
}

Step3: Manage Clients

We will create HTML to display clients list with jQuery Datatables with Ajax.

<table id="clientListing" class="table table-bordered table-striped">
	<thead>
		<tr>
			<th>#</th>
			<th>Name</th>					
			<th>Website</th>					
			<th>Industry</th>
			<th>Phone</th>										
			<th></th>
			<th></th>	
			<th></th>					
		</tr>
	</thead>
</table>

We will initialize DataTable and make ajax request to client_action.php with action listClients to load clients list.

var clientRecords = $('#clientListing').DataTable({
	"lengthChange": false,
	"processing":true,
	"serverSide":true,		
	"bFilter": false,
	'serverMethod': 'post',		
	"order":[],
	"ajax":{
		url:"client_action.php",
		type:"POST",
		data:{action:'listClients'},
		dataType:"json"
	},
	"columnDefs":[
		{
			"targets":[0, 5, 6, 7],
			"orderable":false,
		},
	],
	"pageLength": 10
});	

We will create method listClients() in class Clients.php to return clients list as JSON data.

public function listClients(){		
	$sqlQuery = "SELECT * FROM ".$this->clientsTable." ";
	if(!empty($_POST["search"]["value"])){
		$sqlQuery .= 'where(id LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR name LIKE "%'.$_POST["search"]["value"].'%" ';			
		$sqlQuery .= ' OR website LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR industry LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR phone LIKE "%'.$_POST["search"]["value"].'%") ';								
	}
	
	if(!empty($_POST["order"])){
		$sqlQuery .= 'ORDER BY '.$_POST['order']['0']['column'].' '.$_POST['order']['0']['dir'].' ';
	} else {
		$sqlQuery .= 'ORDER BY id DESC ';
	}
	
	if($_POST["length"] != -1){
		$sqlQuery .= 'LIMIT ' . $_POST['start'] . ', ' . $_POST['length'];
	}
	
	$stmt = $this->conn->prepare($sqlQuery);
	$stmt->execute();
	$result = $stmt->get_result();	
	
	$stmtTotal = $this->conn->prepare("SELECT * FROM ".$this->clientsTable);
	$stmtTotal->execute();
	$allResult = $stmtTotal->get_result();
	$allRecords = $allResult->num_rows;
	
	$displayRecords = $result->num_rows;
	$records = array();		
	while ($client = $result->fetch_assoc()) { 				
		$rows = array();			
		$rows[] = $client['id'];
		$rows[] = ucfirst($client['name']);
		$rows[] = $client['website'];		
		$rows[] = $client['industry'];	
		$rows[] = $client['phone'];			
		$rows[] = '<button type="button" name="view" id="'.$client["id"].'" class="btn btn-info btn-xs view"><span class="glyphicon glyphicon-file" title="View"></span></button>';			
		$rows[] = '<button type="button" name="update" id="'.$client["id"].'" class="btn btn-warning btn-xs update"><span class="glyphicon glyphicon-edit" title="Edit"></span></button>';
		$rows[] = '<button type="button" name="delete" id="'.$client["id"].'" class="btn btn-danger btn-xs delete" ><span class="glyphicon glyphicon-remove" title="Delete"></span></button>';
		$records[] = $rows;
	}
	
	$output = array(
		"draw"	=>	intval($_POST["draw"]),			
		"iTotalRecords"	=> 	$displayRecords,
		"iTotalDisplayRecords"	=>  $allRecords,
		"data"	=> 	$records
	);
	
	echo json_encode($output);
}

We will also handle functionality to add, edit, view and delete functionality. We will insert new clients using insert() methods.

public function insert(){
		
	if($this->name) {

		$stmt = $this->conn->prepare("
		INSERT INTO ".$this->clientsTable."(`name`, `website`, `industry`, `description`, `phone`,`address`,`country`)
		VALUES(?,?,?,?,?,?,?)");
	
		$this->name = htmlspecialchars(strip_tags($this->name));
		$this->website = htmlspecialchars(strip_tags($this->website));
		$this->industry = htmlspecialchars(strip_tags($this->industry));
		$this->description = htmlspecialchars(strip_tags($this->description));
		$this->phone = htmlspecialchars(strip_tags($this->phone));
		$this->address = htmlspecialchars(strip_tags($this->address));	
		$this->country = htmlspecialchars(strip_tags($this->country));				
		
		$stmt->bind_param("sssssss", $this->name, $this->website, $this->industry, $this->description, $this->phone, $this->address, $this->country);
		
		if($stmt->execute()){
			return true;
		}		
	}
}

Step4: Manage Projects

We will create HTML to display project listing with jQuery Datatable with ajax request.

<table id="projectListing" class="table table-bordered table-striped">
	<thead>
		<tr>
			<th>#</th>
			<th>Client</th>					
			<th>Project Name</th>					
			<th>Status</th>
			<th>Project Manager</th>										
			<th>Hourly Rate</th>
			<th>Budget</th>	
			<th></th>	
			<th></th>	
			<th></th>						
		</tr>
	</thead>
</table>

We will initialize jQuery Datatable and make ajax request to project_action.php with action listProjects to display project listing.

var clientRecords = $('#projectListing').DataTable({
	"lengthChange": false,
	"processing":true,
	"serverSide":true,		
	"bFilter": false,
	'serverMethod': 'post',		
	"order":[],
	"ajax":{
		url:"project_action.php",
		type:"POST",
		data:{action:'listProjects'},
		dataType:"json"
	},
	"columnDefs":[
		{
			"targets":[0, 7, 8, 9],
			"orderable":false,
		},
	],
	"pageLength": 10
});	

We will create method listProjects() in class Project.php to get project list and return as JSON data to display jQuery Datatable.

public function listProjects(){
		
	$sqlQuery = "SELECT p.id, p.project_name, p.hourly_rate, p.budget, u.first_name, u.last_name, s.status, c.name as client
		FROM ".$this->projectTable." p 
		LEFT JOIN ".$this->usersTable." u ON u.id = p.project_manager_id
		LEFT JOIN ".$this->statusTable." s ON s.id = p.status_id
		LEFT JOIN ".$this->clientTable." c ON c.id = p.client_id ";
		
	if(!empty($_POST["search"]["value"])){
		$sqlQuery .= ' WHERE (p.project_name LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR p.hourly_rate LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR p.budget LIKE "%'.$_POST["search"]["value"].'%") ';								
	}
	
	if(!empty($_POST["order"])){
		$sqlQuery .= 'ORDER BY '.$_POST['order']['0']['column'].' '.$_POST['order']['0']['dir'].' ';
	} else {
		$sqlQuery .= 'ORDER BY p.id DESC ';
	}
	
	if($_POST["length"] != -1){
		$sqlQuery .= 'LIMIT ' . $_POST['start'] . ', ' . $_POST['length'];
	}
	
	$stmt = $this->conn->prepare($sqlQuery);
	$stmt->execute();
	$result = $stmt->get_result();	
	
	$stmtTotal = $this->conn->prepare("SELECT * FROM ".$this->projectTable);
	$stmtTotal->execute();
	$allResult = $stmtTotal->get_result();
	$allRecords = $allResult->num_rows;
	
	$displayRecords = $result->num_rows;
	$records = array();		
	while ($project = $result->fetch_assoc()) { 				
		$rows = array();			
		$rows[] = $project['id'];
		$rows[] = ucfirst($project['client']);
		$rows[] = ucfirst($project['project_name']);
		$rows[] = $project['status'];		
		$rows[] = ucfirst($project['first_name'])." ".ucfirst($project['last_name']);
		$rows[] = "$".$project['hourly_rate'];
		$rows[] = "$".$project['budget'];			
		$rows[] = '<button type="button" name="view" id="'.$project["id"].'" class="btn btn-info btn-xs view"><span class="glyphicon glyphicon-file" title="View"></span></button>';			
		$rows[] = '<button type="button" name="update" id="'.$project["id"].'" class="btn btn-warning btn-xs update"><span class="glyphicon glyphicon-edit" title="Edit"></span></button>';
		$rows[] = '<button type="button" name="delete" id="'.$project["id"].'" class="btn btn-danger btn-xs delete" ><span class="glyphicon glyphicon-remove" title="Delete"></span></button>';
		$records[] = $rows;
	}
	
	$output = array(
		"draw"	=>	intval($_POST["draw"]),			
		"iTotalRecords"	=> 	$displayRecords,
		"iTotalDisplayRecords"	=>  $allRecords,
		"data"	=> 	$records
	);
	
	echo json_encode($output);
}

We will create new project using insert() method from Project.php class.

public function insert(){
		
	if($this->project_name) {

		$stmt = $this->conn->prepare("
		INSERT INTO ".$this->projectTable."(`project_name`, `client_id`, `project_manager_id`, `active`, `hourly_rate`,`budget`,`status_id`)
		VALUES(?,?,?,?,?,?,?)");
	
		$this->project_name = htmlspecialchars(strip_tags($this->project_name));
		$this->client_id = htmlspecialchars(strip_tags($this->client_id));
		$this->project_manager_id = htmlspecialchars(strip_tags($this->project_manager_id));
		$this->active = htmlspecialchars(strip_tags($this->active));
		$this->hourly_rate = htmlspecialchars(strip_tags($this->hourly_rate));
		$this->budget = htmlspecialchars(strip_tags($this->budget));	
		$this->status_id = htmlspecialchars(strip_tags($this->status_id));				
		
		$stmt->bind_param("siiissi", $this->project_name, $this->client_id, $this->project_manager_id, $this->active, $this->hourly_rate, $this->budget, $this->status_id);
		
		if($stmt->execute()){
			return true;
		}		
	}
}

Step5: Manage Tasks

We will create HTML to display tasks listing with jQuery Datatable.

<table id="taskListing" class="table table-bordered table-striped">
	<thead>
		<tr>
			<th></th>
			<th>Id</th>
			<th>Project Name</th>					
			<th>Task Name</th>					
			<th>Milestone</th>
			<th>Total Hours</th>	
			<th>Status</th>										
			<th>Instructions</th>									
		</tr>
	</thead>
</table>

We will initialize the jQuery Datatable with action listTasks to list the tasks details.

var taskRecords = $('#taskListing').DataTable({
	"lengthChange": false,
	"processing":true,
	"serverSide":true,		
	"bFilter": false,
	'serverMethod': 'post',		
	"order":[],
	"ajax":{
		url:"tasks_action.php",
		type:"POST",
		data:{action:'listTasks'},
		dataType:"json"
	},
	"columnDefs":[
		{
			"targets":[0],
			"orderable":false,
			"className": 'details-control',
			
		}		
	],
	"pageLength": 10
});	

We will create method listTasks() in class Tasks.php to get the list of tasks with details and return as JSON data to display in jQuery Datatable.

public function listTasks(){
		
	$sqlQuery = "SELECT t.id, t.task_name, t.instruction, t.total_hours, s.status, p.project_name, m.name as milestone, u.first_name, u.last_name 
		FROM ".$this->tasksTable." t 
		LEFT JOIN ".$this->projectsTable." p ON p.id = t.project_id
		LEFT JOIN ".$this->milestoneTable." m ON m.id = t.milestone_id
		LEFT JOIN ".$this->userTable." u ON u.id = t.employee_id
		LEFT JOIN ".$this->taskstatusTable." s ON s.id = t.status_id 
		WHERE t.employee_id = '".$_SESSION["userid"]."'";
		
	if(!empty($_POST["search"]["value"])){
		$sqlQuery .= ' AND (t.task_name LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR t.status_id LIKE "%'.$_POST["search"]["value"].'%" ';
		$sqlQuery .= ' OR t.total_hours LIKE "%'.$_POST["search"]["value"].'%") ';								
	}
	
	if(!empty($_POST["order"])){
		$sqlQuery .= 'ORDER BY '.$_POST['order']['0']['column'].' '.$_POST['order']['0']['dir'].' ';
	} else {
		$sqlQuery .= 'ORDER BY t.id DESC ';
	}
	
	if($_POST["length"] != -1){
		$sqlQuery .= 'LIMIT ' . $_POST['start'] . ', ' . $_POST['length'];
	}
	
	$stmt = $this->conn->prepare($sqlQuery);
	$stmt->execute();
	$result = $stmt->get_result();	
	
	$stmtTotal = $this->conn->prepare("SELECT * FROM ".$this->tasksTable." WHERE employee_id = '".$_SESSION["userid"]."'");
	$stmtTotal->execute();
	$allResult = $stmtTotal->get_result();
	$allRecords = $allResult->num_rows;
	
	$displayRecords = $result->num_rows;
	$records = array();		
	while ($task = $result->fetch_assoc()) { 				
		$rows = array();		
		$rows[] = '<span id="'.$task['id'].'">   </span>';
		$rows[] = $task['id'];
		$rows[] = ucfirst($task['project_name']);
		$rows[] = ucfirst($task['task_name']);
		$rows[] = $task['milestone'];		
		$rows[] = $task['total_hours'];
		$rows[] = $task['status'];
		$rows[] = $task['instruction'];						
		$records[] = $rows;
	}
	
	$output = array(
		"draw"	=>	intval($_POST["draw"]),			
		"iTotalRecords"	=> 	$displayRecords,
		"iTotalDisplayRecords"	=>  $allRecords,
		"data"	=> 	$records
	);
	
	echo json_encode($output);
}

We will also display tasks’s hours details by clicking + button to add more hours details for the tasks.

$('#taskListing tbody').on('click', 'td.details-control', function () {
	var tr = $(this).closest('tr');
	var row = taskRecords.row( tr ); 
	if ( row.child.isShown() ) {            
		row.child.hide();
		tr.removeClass('shown');
	} else { 			
		var id = $(this).find('span').attr('id');		
		var action = "getHours";
		$.ajax({
			url:"tasks_action.php",
			method:"POST",
			dataType:"json",
			data:{id:id, action:action},
			success:function(data) {					
				row.child( format(id, data)).show();
				tr.addClass('shown');
			}
		}); 
		
	}
});

We will insert the tasks hours details with insertHours() method from class Tasks.php

public function insertHours(){
		
	if($this->task_id && $this->time && $this->work && $this->date) {

		$stmt = $this->conn->prepare("
		INSERT INTO ".$this->hoursTable."(`date`, `time`, `work_completed`, `task_id`, `employee_id`)
		VALUES(?,?,?,?,?)");
	
		$this->date = htmlspecialchars(strip_tags($this->date));
		$this->time = htmlspecialchars(strip_tags($this->time));
		$this->work = htmlspecialchars(strip_tags($this->work));
		$this->task_id = htmlspecialchars(strip_tags($this->task_id));						
		
		$stmt->bind_param("sssii", $this->date, $this->time, $this->work, $this->task_id, $_SESSION["userid"]);
		
		if($stmt->execute()){
			return true;
		}		
	}
}	

You may also like:

You can view the live demo from the Demo link and can download the script from the Download link below.

Demo Download

12 thoughts on “Project Management System with PHP and MySQL

  1. Employee Add Task button not working in the demo or the downloaded content. The modal for the add Task is missing on the task.php page.

    I keep getting this error: (Uncaught TypeError: $(…).DataTable is not a function) on the floowing code:
    var taskRecords = $(‘#taskListing’).DataTable({
    I moved the jquery lib to the top of the header and still getting the error.

      1. I mean to say ..still Employee –> Add Task is not working even in Demo as well. Thanks. I like this application.

  2. not working, invalid json response and
    Parse error: syntax error, unexpected ‘list’ (T_LIST), expecting identifier (T_STRING) in C:\xampp\htdocs\coba5\class\Clients.php on line 90

  3. Hello,A

    And you can enter the update function without refreshing the page?

    Your tutorials are all great, but I miss the automatic update without refreshing the page.

    Regards

    1. You can check the demo, we have handled record add, update and delete without page refresh. Thanks!

  4. WOW !!! Your blog is very beautiful and many helpful for us. I can learn a lot. Thanks for having us.

Comments are closed.