Black Friday: coupon FRIDAY24 for 40% off Yearly/Lifetime membership! Read more here

Ribbbon - project management system on Laravel 5.1 and Vue.js

We continue our series of reviews of various interesting Laravel projects, and today we have a tool with a weird name Ribbbon (no idea why it's called like that) - it's a simple project management tool with a little different UI than usual Bootstrap-based admin layouts. Let's take a look.


Other reviews in this series:

  1. FlarePoint: Laravel-based free CRM
  2. Invoice Ninja – Laravel-powered solution for better invoicing
  3. Attendize – event tickets selling system based on Laravel 5
  4. Ribbbon – project management system on Laravel 5.1 and Vue.js
  5. Faveo: impressive helpdesk system built on Laravel
  6. Confomo: Laravel-based website to meet Twitter friends at conferences

Ribbbon is available for free on Github - that's where we start our journey. Installation is dev-oriented with usual git clone -> composer install -> .env file -> artisan migrate --seed.

Here's how big is the database:

ribbbon database

As you can see, we've received a default admin login, let's use it.
First, after launching all those commands, we can see a homepage in our browser, ta-daaaa!

ribbbon homepage

Usage

Now, we click Login and see a nice form.

ribbbon login

Login successful, and we see the dashboard of projects and clients.

ribbbon dashboard

Basically, this is all the functionality we have - clients, projects and tasks. Simple. Now, let's to menu item Clients.

We see the list of clients, which looks quite different than I was expecting. It's not really a table, it's a list of clients with already opened detailed view of one of them. You click client's name on the left, and you see details on the right.

ribbbon clients

Also, New Client form is shown in unexpected area - in bottom-right corner, I almost thought it was a live-chat support popup form with a question "Did you like our project, how would you rate it?" or something.

So yeah, you have to get used to the interface, but that's what Vue.js is for, I guess - it's not "another bootstrap project".

Ok, now let's click the Project and see how it looks.

ribbbon projects

List of tasks, similar to Kanban board or Trello. In fact, my first action was drag-drop the task from one column to another. So used to it. Unfortunately, Ribbbon doesn't have that functionality. Instead, when you click a task, you can edit it in a similar popup like client.

ribbbon edit task

Basically, that's it - all the functionality, with a few settings to change if you want. Now, let's dive into the code.


Laravel code

First, what I love about this project, that it's simple and not bloated with packages, to compare with other projects I am reviewing these days. Here's how clean is composer.json:

    "require": {
        "php": ">=5.5.9",
        "laravelcollective/html": "5.1.*",
        "laravel/framework": "5.1.*"
    },
    "require-dev": {
        "fzaninotto/faker": "~1.4",
        "mockery/mockery": "0.9.*",
        "phpunit/phpunit": "~4.0",
        "phpspec/phpspec": "~2.1"
    },

Only Laravel and HTML for forms, that's it! No more dependencies. Now, let's see how it actually works - starting, as usual, from routes file. In this case, it's Laravel 5.1, so it's under app/Http/routes.php:

Route::get('/', 'HomeController@index')--->name('home');
Route::get('register', function(){ return View::make('register')->with('pTitle', "Register"); })->name('register');
Route::get('login', function(){ return View::make('login')->with('pTitle', "Login"); })->name('login');
Route::get('faq', function(){ return View::make('faq')->with('pTitle', "FAQ"); })->name('faq');

//----------------- User routes
Route::resource('users', 'UsersController', array('only' => array('show')));
Route::post('login', 'UsersController@login');
Route::post('make', 'UsersController@register');
Route::get('logout', 'UsersController@logout')->name('logout');
Route::post('resetPassword/{id}','UsersController@resetPassword');

//----------------- Auth routes
Route::group(array('before' => 'auth'), function()
{
    Route::get('hud', 'HomeController@index')->name('hud');
    Route::get('search', 'HomeController@search')->name('search');
    Route::get('profile', 'UsersController@index')->name('profile');
    Route::get('clients', 'ClientsController@index')->name('clients');
    Route::delete('clients/{id}', 'ClientsController@destroy');
    Route::resource('projects', 'ProjectsController', array('only' => array('show')));

});

//----------------- API routes
Route::group(['prefix' => '/api/'], function()
{
    // USER
    Route::get('user', 'UsersController@getUser');
    Route::post('user/{id}', 'UsersController@updateUser');
    Route::delete('user/', 'UsersController@deleteUser');

    // CLIENT
    Route::get('clients/{withWeight?}', 'ClientsController@getAllUserClients');
    Route::put('clients/{id}', 'ClientsController@updateClient');
    Route::post('clients', 'ClientsController@storeClient');
    Route::delete('clients/{id}', 'ClientsController@removeClient');

    // PROJECT
    Route::get('projects/', 'ProjectsController@getAllUserProjects');
    Route::get('projects/shared', 'ProjectsController@getAllMemberProjects');
    Route::get('projects/{id}','ProjectsController@getProject');
    Route::get('projects/{id}/owner','ProjectsController@getOwner');
    Route::get('projects/{id}/members','ProjectsController@getMembers');
    Route::post('projects', 'ProjectsController@storeProject');
    Route::put('projects/{id}', 'ProjectsController@updateProject');
    Route::post('projects/{id}/{email}/invite', 'ProjectsController@invite');
    Route::delete('projects/{id}/{member_id}/remove', 'ProjectsController@removeMember' );

    // TASK
    Route::get('tasks', 'TasksController@getAllUserOpenTasks');
    Route::post('tasks/{client_id}/{project_id}', 'TasksController@storeTask');
    Route::delete('tasks/{id}', 'TasksController@removeTask');
    Route::put('tasks/{id}', 'TasksController@updateTask');

    // CREDENTIALS
    Route::get('credentials/{id}','CredentialsController@getProjectCredentials');
    Route::post('credentials', 'CredentialsController@storeCredential');
    Route::put('credentials/{id}', 'CredentialsController@updateCredential');
    Route::delete('credentials/{id}', 'CredentialsController@removeCredential');
});

//----------------- Admin routes
Route::get('admin','AdminController@index');

As you can see, not a big list of routes, with only one Resource Controller, and all other actions are described directly via get/post/put/delete.

To be honest, I'm a fan of naming routes, for clarity and re-usage in the future, so I would add the names to this project as well.

Also, as you can see, only a few routes are client-facing, most of the actions and data come from the API which is put into its separate group. It will make even more sense when we see ClientsController.php:

class ClientsController extends BaseController {
    // Go to clients index page
    public function index()
    {
        return View::make('ins/clients/index')->with("pTitle", "Clients");
    }

See, no data is loaded here, only a view. Which looks like this:

@extends('templates/ins/master')

@section('content')

<div id="client">
<div class="row">
<div class="col-xs-12 page-title-section">
<h1 class="pull-left">Clients</h1>
<a class="btn btn-primary pull-right" title="Create new client">+ New Client</a>
<div class="clearfix"> </div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="mega-menu">
<div class="links"><a data-id="client_@{{client.id}}"> @{{client.name}} </a></div>
<div class="content">
<div id="client_@{{client.id}}" class="item" title="Edit client">

See, Vue.js in action. And at the end of file it has:

<script src="{{ asset('assets/js/controllers/client.js') }}"></script>

This is where the API calls happen - in client.js:

var client = new Vue({
  el: '#client',
  data: {
    clients: [],
    client: null,
    currentClient: null,
    newProjectClientId: {name: null, project_id: null},
    newProject: {name: null, project_id: null},
    tempClientIndex: null,
    lastRequest: false,
    msg: {success: null, error: null}
  },

  ready: function(){
    this.getClients();
  },

  methods: {
    getClients: function(){
        $.get( window.baseurl + "/api/clients/true", function( results ) {
            client.clients = results.data;
            Vue.nextTick(function () {
                megaMenuInit();
            })
        }).fail(function(e){
            console.log( e );
        });
    },
    showCreateForm: function(){
          this.msg.success = null;
          this.msg.error = null;
          $(".new-client").show();
          $(".new-client .first").focus();
      },
    create: function(new_client, update){

        update = update || false;

        $.ajax({
          type: 'POST',
          url: window.baseurl + "/api/clients",
          data: new_client,
          error: function(e) {
            var response = jQuery.parseJSON(e.responseText);
            $('.new-client .status-msg').text("")
                                        .removeClass('success-msg')
                                        .addClass("error-msg")
                                        .text(response.message);
            return false;
          },

A lot of code, I agree. But quite useful to analyze, don't you think?

What I would change in terms of the structure - I would move API functionality to separate controllers, whereas the author here has the same ClientController for both showing homepage and API calls. Convenient maybe, but quite hard to re-use in the future if there's more functionality in plans.

Also, it seems that the code was written in Laravel 4 and only then transformed in Laravel 5, I see a lot of Input::all() and similar lines in the code.

But that's fine, as long as it works, right?

In conclusion, Ribbbon seems like a nice small app to analyze for practice in Laravel, especially grouped with Vue.js. In terms of project management - with so many others on the market for free, I wouldn't personally use Ribbbon, but maybe you will?


Other reviews in this series:

 

  1. FlarePoint: Laravel-based free CRM
  2. Invoice Ninja – Laravel-powered solution for better invoicing
  3. Attendize – event tickets selling system based on Laravel 5
  4. Ribbbon – project management system on Laravel 5.1 and Vue.js
  5. Faveo: impressive helpdesk system built on Laravel
  6. Confomo: Laravel-based website to meet Twitter friends at conferences

No comments or questions yet...

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 67 courses (1172 lessons, total 43 h 18 min)
  • 90 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent New Courses