Implemented dashboard
50
app/controllers/DashboardController.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
class DashboardController extends \BaseController {
|
||||
|
||||
public function index()
|
||||
{
|
||||
// total_income, billed_clients, invoice_sent and active_clients
|
||||
$select = DB::raw('SUM(clients.paid_to_date) total_income,
|
||||
COUNT(DISTINCT CASE WHEN invoices.id IS NOT NULL THEN clients.id ELSE null END) - 1 billed_clients,
|
||||
SUM(CASE WHEN invoices.invoice_status_id >= '.INVOICE_STATUS_SENT.' THEN 1 ELSE 0 END) invoices_sent,
|
||||
COUNT(DISTINCT clients.id) active_clients');
|
||||
|
||||
$metrics = DB::table('clients')
|
||||
->select($select)
|
||||
->leftJoin('invoices', 'clients.id', '=', 'invoices.client_id')
|
||||
->where('clients.account_id', '=', Auth::user()->account_id)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->groupBy('clients.account_id')
|
||||
->first();
|
||||
|
||||
$invoiceAvg = DB::table('invoices')
|
||||
->where('invoices.account_id', '=', Auth::user()->account_id)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->avg('amount');
|
||||
|
||||
|
||||
$activities = Activity::where('activities.account_id', '=', Auth::user()->account_id)
|
||||
->orderBy('created_at', 'desc')->take(6)->get();
|
||||
|
||||
$pastDue = Invoice::scope()->where('due_date', '<', date('Y-m-d'))
|
||||
->orderBy('due_date', 'asc')->take(6)->get();
|
||||
|
||||
$upcoming = Invoice::scope()->where('due_date', '>', date('Y-m-d'))
|
||||
->orderBy('due_date', 'asc')->take(6)->get();
|
||||
|
||||
$data = [
|
||||
'totalIncome' => Utils::formatMoney($metrics->total_income, Session::get(SESSION_CURRENCY)),
|
||||
'billedClients' => $metrics->billed_clients,
|
||||
'invoicesSent' => $metrics->invoices_sent,
|
||||
'activeClients' => $metrics->active_clients,
|
||||
'invoiceAvg' => Utils::formatMoney($invoiceAvg, Session::get(SESSION_CURRENCY)),
|
||||
'activities' => $activities,
|
||||
'pastDue' => $pastDue,
|
||||
'upcoming' => $upcoming
|
||||
];
|
||||
|
||||
return View::make('dashboard', $data);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddCascaseDrops extends Migration {
|
||||
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('invoices', function($table)
|
||||
{
|
||||
$table->dropForeign('invoices_account_id_foreign');
|
||||
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,11 @@ class Invoice extends EntityModel
|
||||
return $this->invoice_number;
|
||||
}
|
||||
|
||||
public function getLink()
|
||||
{
|
||||
return link_to('invoices/' . $this->public_id, $this->invoice_number);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return ENTITY_INVOICE;
|
||||
|
@ -12,14 +12,14 @@ class InvoiceRepository
|
||||
{
|
||||
$query = \DB::table('invoices')
|
||||
->join('clients', 'clients.id', '=','invoices.client_id')
|
||||
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->join('invoice_statuses', 'invoice_statuses.id', '=', 'invoices.invoice_status_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->where('invoices.account_id', '=', $accountId)
|
||||
->where('invoices.deleted_at', '=', null)
|
||||
->where('clients.deleted_at', '=', null)
|
||||
->where('invoices.is_recurring', '=', false)
|
||||
->where('contacts.is_primary', '=', true)
|
||||
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
|
||||
->select('clients.public_id as client_public_id', 'invoice_number', 'clients.name as client_name', 'invoices.public_id', 'amount', 'invoices.balance', 'invoice_date', 'due_date', 'invoice_statuses.name as invoice_status_name', 'clients.currency_id', 'contacts.first_name', 'contacts.last_name', 'contacts.email');
|
||||
|
||||
if ($clientPublicId)
|
||||
{
|
||||
|
@ -85,7 +85,7 @@ Route::get('logout', 'UserController@logout');
|
||||
|
||||
Route::group(array('before' => 'auth'), function()
|
||||
{
|
||||
Route::get('home', function() { return View::make('header'); });
|
||||
Route::get('dashboard', 'DashboardController@index');
|
||||
Route::get('account/getSearchData', array('as' => 'getSearchData', 'uses' => 'AccountController@getSearchData'));
|
||||
Route::get('account/{section?}', 'AccountController@showSection');
|
||||
Route::post('account/{section?}', 'AccountController@doSection');
|
||||
|
141
app/views/dashboard.blade.php
Normal file
@ -0,0 +1,141 @@
|
||||
@extends('header')
|
||||
|
||||
@section('content')
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<img src="{{ asset('images/totalincome.png') }}" class="in-image"/>
|
||||
<div class="in-bold">
|
||||
{{ $totalIncome }}
|
||||
</div>
|
||||
<div class="in-thin">
|
||||
in total income
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<img src="{{ asset('images/clients.png') }}" class="in-image"/>
|
||||
<div class="in-bold">
|
||||
{{ $billedClients }}
|
||||
</div>
|
||||
<div class="in-thin">
|
||||
{{ Utils::pluralize('billed client', $billedClients) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<img src="{{ asset('images/totalinvoices.png') }}" class="in-image"/>
|
||||
<div class="in-bold">
|
||||
{{ $invoicesSent }}
|
||||
</div>
|
||||
<div class="in-thin">
|
||||
{{ Utils::pluralize('invoice', $invoicesSent) }} sent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<p> </p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" style="min-height:320px">
|
||||
<div class="panel-heading" style="background-color:#2299c0">
|
||||
<h3 class="panel-title in-bold-white">
|
||||
<i class="glyphicon glyphicon-exclamation-sign"></i> Notifications
|
||||
</h3>
|
||||
</div>
|
||||
<ul class="panel-body list-group">
|
||||
@foreach ($activities as $activity)
|
||||
<li class="list-group-item">
|
||||
<span style="color:#888;font-style:italic">{{ Utils::timestampToDateTimeString(strtotime($activity->created_at)) }}:</span>
|
||||
{{ Utils::decodeActivity($activity->message) }}
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" style="min-height:320px">
|
||||
<div class="panel-heading" style="background-color:#e37329">
|
||||
<h3 class="panel-title in-bold-white">
|
||||
<i class="glyphicon glyphicon-time"></i> Invoices Past Due
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Invoice #</th>
|
||||
<th>Client</th>
|
||||
<th>Due date</th>
|
||||
<th>Balance due</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($pastDue as $invoice)
|
||||
<tr>
|
||||
<td>{{ $invoice->getLink() }}</td>
|
||||
<td>{{ $invoice->client->getDisplayName() }}</td>
|
||||
<td>{{ Utils::fromSqlDate($invoice->due_date) }}</td>
|
||||
<td>{{ Utils::formatMoney($invoice->balance, $invoice->client->currency_id) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default" style="min-height:320px">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
<i class="glyphicon glyphicon-time"></i> Upcoming invoices
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th>Invoice #</th>
|
||||
<th>Client</th>
|
||||
<th>Due date</th>
|
||||
<th>Balance due</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($upcoming as $invoice)
|
||||
<tr>
|
||||
<td>{{ $invoice->getLink() }}</td>
|
||||
<td>{{ $invoice->client->getDisplayName() }}</td>
|
||||
<td>{{ Utils::fromSqlDate($invoice->due_date) }}</td>
|
||||
<td>{{ Utils::formatMoney($invoice->balance, $invoice->client->currency_id) }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-6 active-clients">
|
||||
<div class="in-bold in-white" style="font-size:42px">{{ $activeClients }}</div>
|
||||
<div class="in-thin in-white">{{ Utils::pluralize('active client', $activeClients) }}</div>
|
||||
</div>
|
||||
<div class="col-md-6 average-invoice">
|
||||
<div><b>average invoice</b></div>
|
||||
<div class="in-bold in-white" style="font-size:42px">{{ $invoiceAvg }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@stop
|
@ -77,12 +77,14 @@
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="{{ URL::to('/rocksteady') }}" class='navbar-brand'>Invoice Ninja</a>
|
||||
<a href="{{ URL::to('/rocksteady') }}" class='navbar-brand'>
|
||||
<img src="{{ asset('images/invoiceninja-logo.png') }}" style="height:18px;width:auto"/>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbar-collapse-1">
|
||||
<ul class="nav navbar-nav" style="font-weight: bold">
|
||||
{{-- HTML::nav_link('home', 'Home') --}}
|
||||
{{ HTML::nav_link('dashboard', 'Dashboard') }}
|
||||
{{ HTML::menu_link('client') }}
|
||||
{{ HTML::menu_link('invoice') }}
|
||||
{{ HTML::menu_link('payment') }}
|
||||
@ -121,7 +123,7 @@
|
||||
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Recently Viewed <b class="caret"></b></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">History <b class="caret"></b></a>
|
||||
<ul class="dropdown-menu">
|
||||
@if (count(Session::get(RECENTLY_VIEWED)) == 0)
|
||||
<li><a href="#">No items</a></li>
|
||||
|
@ -24,7 +24,7 @@
|
||||
<div class="form-group">
|
||||
<label for="client" class="control-label col-lg-4 col-sm-4">Client</label>
|
||||
<div class="col-lg-8 col-sm-8" style="padding-top: 7px">
|
||||
<a id="editClientLink" href="#" data-bind="click: $root.showClientForm">{{ $client->getDisplayName() }}</a>
|
||||
<a id="editClientLink" class="pointer" data-bind="click: $root.showClientForm">{{ $client->getDisplayName() }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:none">
|
||||
@ -34,7 +34,7 @@
|
||||
|
||||
<div class="form-group" style="margin-bottom: 8px">
|
||||
<div class="col-lg-8 col-sm-8 col-lg-offset-4 col-sm-offset-4">
|
||||
<a id="createClientLink" data-bind="click: $root.showClientForm, text: $root.clientLinkText"></a>
|
||||
<a id="createClientLink" class="pointer" data-bind="click: $root.showClientForm, text: $root.clientLinkText"></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,15 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
||||
<title>Invoice Ninja {{ isset($title) ? $title : '' }}</title>
|
||||
<link rel="canonical" href="https://www.invoiceninja.com"></link>
|
||||
<link href="{{ asset('favicon.ico') }}" rel="icon" type="image/x-icon">
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="{{ asset('favicon.ico') }}" rel="icon" type="image/x-icon">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta property="og:site_name" content="Invoice Ninja"></meta>
|
||||
<meta property="og:url" content="https://www.invoiceninja.com"></meta>
|
||||
<meta property="og:title" content="Invoice Ninja"></meta>
|
||||
<meta property="og:image" content="https://fbcdn-sphotos-b-a.akamaihd.net/hphotos-ak-ash3/t31/1548037_274756319355261_10423754_o.jpg"></meta>
|
||||
<meta property="og:description" content="Simple, Intuitive Invoicing."></meta>
|
||||
<meta name="keywords" content="Invoice Ninja"></meta>
|
||||
|
||||
<title>Invoice Ninja {{ isset($title) ? $title : '' }}</title>
|
||||
|
||||
<script src="{{ asset('vendor/jquery/jquery.min.js') }}" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
window.onerror = function(e) {
|
||||
|
415
composer.lock
generated
@ -19,6 +19,10 @@ div.panel {
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
|
||||
.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
@ -86,6 +90,20 @@ table.table thead .sorting { background: url('') no-repeat center right; }
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: #0b4d78 !important;
|
||||
background-image: none;
|
||||
background-repeat: no-repeat;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.navbar .active > a {
|
||||
background-color: #09334f !important;
|
||||
background-image: none;
|
||||
background-repeat: no-repeat;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.navbar .sub-menu:before {
|
||||
border-bottom: 7px solid transparent;
|
||||
border-left: none;
|
||||
@ -105,6 +123,52 @@ table.table thead .sorting { background: url('') no-repeat center right; }
|
||||
}
|
||||
|
||||
|
||||
/*******************
|
||||
Dashboard
|
||||
*******************/
|
||||
|
||||
.in-bold {
|
||||
font-size: 26px;
|
||||
font-weight: bold;;
|
||||
}
|
||||
|
||||
|
||||
.in-thin {
|
||||
font-size: 26px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.in-bold-white {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.in-image {
|
||||
float:left;padding-right:16px;
|
||||
}
|
||||
|
||||
.in-white {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.active-clients {
|
||||
background-color: #2299c0;
|
||||
background-image:url('../images/activeclients.png');
|
||||
background-position:center;
|
||||
background-repeat: no-repeat;
|
||||
height: 200px;
|
||||
padding-top: 44px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.average-invoice {
|
||||
background-color: #ecd817;
|
||||
height: 200px;
|
||||
padding-top: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.invoice-table tbody {
|
||||
border-style: none !important;
|
||||
}
|
||||
|
BIN
public/images/activeclients.png
Normal file
After Width: | Height: | Size: 3.8 KiB |
BIN
public/images/activeclients@2x.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
public/images/clients.png
Normal file
After Width: | Height: | Size: 3.7 KiB |
BIN
public/images/clients@2x.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
BIN
public/images/totalincome.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
public/images/totalincome@2x.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/totalinvoices.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
public/images/totalinvoices@2x.png
Normal file
After Width: | Height: | Size: 5.5 KiB |