In this series we will implement a CRM system with laravel. This is a long series so you need to keep track of every part carefully to understand all the basic points.
Series Topics:
- Part 1: Preparation
- Part 2: Database
- Part 3: Models & Relations
- Part 4: Preparing Login Page
- Part 5: Users Module
- Part 6: Roles & Permissions
- Part 7: Documents Module
- Part 8: Contacts Module
- Part 9: Tasks Module
- Part 10: Mailbox Module
- Part 11: Mailbox Module Complete
- Part 12: Calendar Module
- Part 13: Finishing
Requirements
- Laravel 5.6 or higher.
- spatia/laravel-permission package.
- appzcoder/crud-generator package.
What you will learn in this series
- A basic understanding of the CRM system.
- A basic understanding of the CRM components.
- How to use the roles and permissions in laravel and how to utilize this in the CRM system.
- How to use the crud generator to generate CRM modules.
Overview
Basically a CRM (Customer Relationship Managment) is a system that manages the organization relationships and interactions with customers, so the main core component is the customer. The customer in the context of the CRM has a lot of statuses and passes through a cycle of stages in other terms called a pipeline until he becomes the organization loyal customer.
Â
Who uses the CRM
- Sales people
- Sometimes marketing people
Â
CRM Components
1. Contacts
The main component in a typical CRM system is the contacts and it has the following levels:
- Leads: This is the first stage of the contact, a lead is a contact information of someone that he is looking to purchase a service but may or may not purchase the service.
- Opportunity: This is the second stage of the contact, opportunity is a qualified lead which has shown some interest to the service you are offering.
- Customer/won: This is the third stage, a customer/won is the qualified customer and in this stage the contact has taken the decision to surly purchase your service and become the organization loyal customer.
- Closed: This stage is for failed leads and opportunities as we mentioned is that the lead may not be interested in your service in this case it’s marked as closed.
Â
2. Tasks
Tasks is what the required service the customer wants, it must have a status indicator to show that it completes or it’s in progress.
Â
3. Documents
This represents any attachments whether images, word documents or pdf files.
Â
4. Emails & Messages
The CRM core system must send emails when specific action happen such as when a task is assigned to a user. Also there must be a messages module to enable users to send messages to each other.
Â
5. Users
Those are the sales and the sales managers.
Â
6. Roles & Permissions
Represents the roles and permissions assigned to each user.
7. Calendars
This is a less important component but it’s required in some CRM systems it helps users to monitor their task status by date.
Prepare project
Let’s create a new laravel application:
composer create-project laravel/laravel crm 5.6 --prefer-dist
Next install the spatie/laravel-permission, we will use that package for handling roles and permissions:
composer require spatie/laravel-permission
Open config/app.php
and add this line to the providers array:
'providers' => [ // ... Spatie\Permission\PermissionServiceProvider::class, ];
Publish package config file:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
Publish package migrations:
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
This command will add the migration files for this package.
Preparing admin layout
We will use the popular AdminLTE html template as the admin layout, just download it from here.
After downloading the template extract it and then create a new folder called “theme” in the public directory of your laravel project and open the extracted template folder and move these folders “bower_components“, “plugins“, “dist” inside /public/theme directory you just created.
Then Create those folders inside resources/views:
- layout: This will hold the main layout
- pages: This will hold the admin modules
Add the below files inside resources/views/layout:
resources/views/layout/app.blade.php
<!doctype html> <html lang="{{ app()->getLocale() }}"> <head> <meta charset="utf-8"> <title>CRM @yield('title')</title> <meta name="csrf_token" content="{{ csrf_token() }}" /> @include('layout.styles') <script> var BASE_URL = '{{ url("/") }}'; </script> </head> <body class="hold-transition skin-blue sidebar-mini"> <div class="wrapper"> @include('layout.header') @include('layout.sidebar') <!-- Content Wrapper --> <div class="content-wrapper"> @yield('content') </div> </div> @include('layout.footer') @yield('scripts') </body> </html>
resources/views/layout/styles.blade.php
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bootstrap/dist/css/bootstrap.min.css"> <!-- Font Awesome --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}font-awesome/css/font-awesome.min.css"> <!-- Ionicons --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}Ionicons/css/ionicons.min.css"> <!-- Theme style --> <link rel="stylesheet" href="{{ url('theme') . '/dist/' }}/css/AdminLTE.min.css"> <!-- AdminLTE Skins. Choose a skin from the css/skins folder instead of downloading all of them to reduce the load. --> <link rel="stylesheet" href="{{ url('theme') . '/dist/' }}/css/skins/_all-skins.min.css"> <!-- iCheck for checkboxes and radio inputs --> <link rel="stylesheet" href="{{ url('theme') . '/plugins/' }}/iCheck/all.css"> <!-- Date Picker --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bootstrap-datepicker/dist/css/bootstrap-datepicker.min.css"> <!-- Daterange picker --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bootstrap-daterangepicker/daterangepicker.css"> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}/select2/dist/css/select2.css"> <!-- bootstrap wysihtml5 - text editor --> <link rel="stylesheet" href="{{ url('theme') . '/plugins/' }}/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <!-- Google Font --> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> <!-- For page specific styles --> @yield('styles')
resources/views/layout/header.blade.php
<header class="main-header"> <!-- Logo --> <a href="{{ url('/admin') }}" class="logo"> <!-- mini logo for sidebar mini 50x50 pixels --> <span class="logo-mini"><b>M</b>CRM</span> <!-- logo for regular state and mobile devices --> <span class="logo-lg"><b>Mini</b>CRM</span> </a> <!-- Header Navbar: style can be found in header.less --> <nav class="navbar navbar-static-top"> <!-- Sidebar toggle button--> <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button"> <span class="sr-only">Toggle navigation</span> </a> <div class="navbar-custom-menu"> <ul class="nav navbar-nav"> <!-- Messages: style can be found in dropdown.less--> <li class="dropdown messages-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <i class="fa fa-envelope-o"></i> <span class="label label-success">4</span> </a> <ul class="dropdown-menu"> <li class="header">You have 4 messages</li> <li> <!-- inner menu: contains the actual data --> <ul class="menu"> <li><!-- start message --> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user2-160x160.jpg" class="img-circle" alt="User Image"> </div> <h4> Support Team <small><i class="fa fa-clock-o"></i> 5 mins</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <!-- end message --> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user3-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> AdminLTE Design Team <small><i class="fa fa-clock-o"></i> 2 hours</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user4-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Developers <small><i class="fa fa-clock-o"></i> Today</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user3-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Sales Department <small><i class="fa fa-clock-o"></i> Yesterday</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user4-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Reviewers <small><i class="fa fa-clock-o"></i> 2 days</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> </ul> </li> <li class="footer"><a href="#">See All Messages</a></li> </ul> </li> <!-- User Account: style can be found in dropdown.less --> <li class="dropdown user user-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <span class="hidden-xs">Alexander Pierce</span> </a> <ul class="dropdown-menu"> <!-- User image --> <li class="user-header"> <p> Alexander Pierce - Web Developer <small>Member since Nov. 2012</small> </p> </li> <!-- Menu Footer--> <li class="user-footer"> <div class="pull-left"> <a href="#" class="btn btn-default btn-flat">Profile</a> </div> <div class="pull-right"> <a href="#" class="btn btn-default btn-flat">Sign out</a> </div> </li> </ul> </li> </ul> </div> </nav> </header>
resources/views/layout/sidebar.blade.php
<!-- Left side column. contains the logo and sidebar --> <aside class="main-sidebar"> <!-- sidebar: style can be found in sidebar.less --> <section class="sidebar"> <!-- Sidebar user panel --> <div class="user-panel"> <div class="pull-left image"> <img src="{{ url('theme') . '/dist/' }}/img/user2-160x160.jpg" class="img-circle" alt="User Image"> </div> <div class="pull-left info"> <p>Alexander Pierce</p> </div> </div> <!-- sidebar menu: : style can be found in sidebar.less --> <ul class="sidebar-menu" data-widget="tree"> <li class="header">MAIN NAVIGATION</li> <li class="active treeview"> <a href="#"> <i class="fa fa-dashboard"></i> <span>Dashboard</span> <span class="pull-right-container"> <i class="fa fa-angle-left pull-right"></i> </span> </a> <ul class="treeview-menu"> <li class="active"><a href="#"><i class="fa fa-circle-o"></i> Home</a></li> </ul> </li> </ul> </section> <!-- /.sidebar --> </aside>
resources/views/layout/footer.blade.php
<!-- jQuery 3 --> <script src="{{ url('theme') . '/bower_components/' }}jquery/dist/jquery.min.js"></script> <!-- jQuery UI 1.11.4 --> <script src="{{ url('theme') . '/bower_components/' }}jquery-ui/jquery-ui.min.js"></script> <!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip --> <script> $.widget.bridge('uibutton', $.ui.button); </script> <!-- Bootstrap 3.3.7 --> <script src="{{ url('theme') . '/bower_components/' }}bootstrap/dist/js/bootstrap.min.js"></script> <!-- Morris.js charts --> <script src="{{ url('theme') . '/bower_components/' }}raphael/raphael.min.js"></script> <script src="{{ url('theme') . '/bower_components/' }}morris.js/morris.min.js"></script> <!-- Sparkline --> <script src="{{ url('theme') . '/bower_components/' }}jquery-sparkline/dist/jquery.sparkline.min.js"></script> <!-- jvectormap --> <script src="{{ url('theme') . '/plugins/' }}/jvectormap/jquery-jvectormap-1.2.2.min.js"></script> <script src="{{ url('theme') . '/plugins/' }}/jvectormap/jquery-jvectormap-world-mill-en.js"></script> <!-- jQuery Knob Chart --> <script src="{{ url('theme') . '/bower_components/' }}jquery-knob/dist/jquery.knob.min.js"></script> <!-- daterangepicker --> <script src="{{ url('theme') . '/bower_components/' }}moment/min/moment.min.js"></script> <script src="{{ url('theme') . '/bower_components/' }}bootstrap-daterangepicker/daterangepicker.js"></script> <!-- datepicker --> <script src="{{ url('theme') . '/bower_components/' }}bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js"></script> <!-- Bootstrap WYSIHTML5 --> <script src="{{ url('theme') . '/plugins/' }}/bootstrap-wysihtml5/bootstrap3-wysihtml5.all.min.js"></script> <!-- Slimscroll --> <script src="{{ url('theme') . '/bower_components/' }}jquery-slimscroll/jquery.slimscroll.min.js"></script> <!-- iCheck 1.0.1 --> <script src="{{ url('theme') . '/plugins/' }}/iCheck/icheck.min.js"></script> <!-- FastClick --> <script src="{{ url('theme') . '/bower_components/' }}fastclick/lib/fastclick.js"></script> <!-- Select2 --> <script src="{{ url('theme') . '/bower_components/' }}/select2/dist/js/select2.js"></script> <!-- AdminLTE App --> <script src="{{ url('theme') . '/dist/' }}/js/adminlte.min.js"></script> <!-- AdminLTE dashboard demo (This is only for demo purposes) --> {{--<script src="{{ url('theme') . '/dist/' }}/js/pages/dashboard.js"></script>--}} <!-- AdminLTE for demo purposes --> <script src="{{ url('theme') . '/dist/' }}/js/demo.js"></script> <!-- Custom script for initializing widgets --> <script src="{{ url('theme') }}/custom.js"></script>
public/theme/custom.js
$(function () { //iCheck for checkbox and radio inputs $('input[type="checkbox"].minimal, input[type="radio"].minimal').iCheck({ checkboxClass: 'icheckbox_minimal-blue', radioClass : 'iradio_minimal-blue' }) //Red color scheme for iCheck $('input[type="checkbox"].minimal-red, input[type="radio"].minimal-red').iCheck({ checkboxClass: 'icheckbox_minimal-red', radioClass : 'iradio_minimal-red' }) //Flat red color scheme for iCheck $('input[type="checkbox"].flat-red, input[type="radio"].flat-red').iCheck({ checkboxClass: 'icheckbox_flat-green', radioClass : 'iradio_flat-green' }) });
Next check that the template is working so let’s create the first page of our admin which is the homepage.
Let’s create a new folder inside resources/views/pages called “home” and inside it add the below file:
resources/views/pages/home/index.blade.php
@extends('layout.app') @section('title', ' | Dashboard') @section('content') <!-- Content Header (Page header) --> <section class="content-header"> <h1> Dashboard <small>Control panel</small> </h1> <ol class="breadcrumb"> <li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li> <li class="active">Dashboard</li> </ol> </section> @stop
open routes/web.php and modify it as follows:
<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::get('/', function () { return view('pages.home.index'); });
Now navigate to http://localhost/crm/public.
Continue to part 2: Database>>>
You say in this article that we have to copy the bower_components folder, but i would like to update the package font-awesome. How can i do it?
Do i install first the package bower? Which is the solution?
Thanks
If you need to update the font-awesome you can pick the latest cdn from https://cdnjs.com/libraries/font-awesome but be careful i am not sure that the fonts load correctly