mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2024-11-08 12:12:48 +01:00
Working on the dashboard
This commit is contained in:
parent
c895cb2bbd
commit
ce3a510037
@ -135,7 +135,8 @@ class CreateTestData extends Command
|
|||||||
$data = [
|
$data = [
|
||||||
'invoice_id' => $invoice->id,
|
'invoice_id' => $invoice->id,
|
||||||
'client_id' => $client->id,
|
'client_id' => $client->id,
|
||||||
'amount' => $this->faker->randomFloat(2, 0, $invoice->amount)
|
'amount' => $this->faker->randomFloat(2, 0, $invoice->amount),
|
||||||
|
'payment_date_sql' => date_create()->modify(rand(-100, 100) . ' days')->format('Y-m-d'),
|
||||||
];
|
];
|
||||||
|
|
||||||
$payment = $this->paymentRepo->save($data);
|
$payment = $this->paymentRepo->save($data);
|
||||||
|
@ -4,6 +4,8 @@ use stdClass;
|
|||||||
use Auth;
|
use Auth;
|
||||||
use DB;
|
use DB;
|
||||||
use View;
|
use View;
|
||||||
|
use Utils;
|
||||||
|
use App\Models\Client;
|
||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\Payment;
|
use App\Models\Payment;
|
||||||
use App\Ninja\Repositories\DashboardRepository;
|
use App\Ninja\Repositories\DashboardRepository;
|
||||||
@ -26,7 +28,8 @@ class DashboardController extends BaseController
|
|||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
$viewAll = $user->hasPermission('view_all');
|
$viewAll = $user->hasPermission('view_all');
|
||||||
$userId = $user->id;
|
$userId = $user->id;
|
||||||
$accountId = $user->account->id;
|
$account = $user->account;
|
||||||
|
$accountId = $account->id;
|
||||||
|
|
||||||
$dashboardRepo = $this->dashboardRepo;
|
$dashboardRepo = $this->dashboardRepo;
|
||||||
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
|
$metrics = $dashboardRepo->totals($accountId, $userId, $viewAll);
|
||||||
@ -37,7 +40,10 @@ class DashboardController extends BaseController
|
|||||||
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
|
$pastDue = $dashboardRepo->pastDue($accountId, $userId, $viewAll);
|
||||||
$upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll);
|
$upcoming = $dashboardRepo->upcoming($accountId, $userId, $viewAll);
|
||||||
$payments = $dashboardRepo->payments($accountId, $userId, $viewAll);
|
$payments = $dashboardRepo->payments($accountId, $userId, $viewAll);
|
||||||
|
$expenses = $dashboardRepo->expenses($accountId, $userId, $viewAll);
|
||||||
|
$tasks = $dashboardRepo->tasks($accountId, $userId, $viewAll);
|
||||||
|
|
||||||
|
// check if the account has quotes
|
||||||
$hasQuotes = false;
|
$hasQuotes = false;
|
||||||
foreach ([$upcoming, $pastDue] as $data) {
|
foreach ([$upcoming, $pastDue] as $data) {
|
||||||
foreach ($data as $invoice) {
|
foreach ($data as $invoice) {
|
||||||
@ -47,6 +53,26 @@ class DashboardController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if the account has multiple curencies
|
||||||
|
$currencyIds = $account->currency_id ? [$account->currency_id] : [DEFAULT_CURRENCY];
|
||||||
|
$data = Client::scope()
|
||||||
|
->withArchived()
|
||||||
|
->distinct()
|
||||||
|
->get(['currency_id'])
|
||||||
|
->toArray();
|
||||||
|
|
||||||
|
array_map(function ($item) use (&$currencyIds) {
|
||||||
|
$currencyId = intval($item['currency_id']);
|
||||||
|
if ($currencyId && ! in_array($currencyId, $currencyIds)) {
|
||||||
|
$currencyIds[] = $currencyId;
|
||||||
|
}
|
||||||
|
}, $data);
|
||||||
|
|
||||||
|
$currencies = [];
|
||||||
|
foreach ($currencyIds as $currencyId) {
|
||||||
|
$currencies[$currencyId] = Utils::getFromCache($currencyId, 'currencies')->code;
|
||||||
|
}
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
'account' => $user->account,
|
'account' => $user->account,
|
||||||
'paidToDate' => $paidToDate,
|
'paidToDate' => $paidToDate,
|
||||||
@ -60,8 +86,20 @@ class DashboardController extends BaseController
|
|||||||
'payments' => $payments,
|
'payments' => $payments,
|
||||||
'title' => trans('texts.dashboard'),
|
'title' => trans('texts.dashboard'),
|
||||||
'hasQuotes' => $hasQuotes,
|
'hasQuotes' => $hasQuotes,
|
||||||
|
'showBreadcrumbs' => false,
|
||||||
|
'currencies' => $currencies,
|
||||||
|
'expenses' => $expenses,
|
||||||
|
'tasks' => $tasks,
|
||||||
];
|
];
|
||||||
|
|
||||||
return View::make('dashboard', $data);
|
return View::make('dashboard', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function chartData($groupBy, $startDate, $endDate, $currencyCode, $includeExpenses)
|
||||||
|
{
|
||||||
|
$includeExpenses = filter_var($includeExpenses, FILTER_VALIDATE_BOOLEAN);
|
||||||
|
$data = $this->dashboardRepo->chartData(Auth::user()->account, $groupBy, $startDate, $endDate, $currencyCode, $includeExpenses);
|
||||||
|
|
||||||
|
return json_encode($data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,6 @@ use Config;
|
|||||||
use Input;
|
use Input;
|
||||||
use Utils;
|
use Utils;
|
||||||
use DB;
|
use DB;
|
||||||
use DateInterval;
|
|
||||||
use DatePeriod;
|
|
||||||
use Session;
|
use Session;
|
||||||
use View;
|
use View;
|
||||||
use App\Models\Account;
|
use App\Models\Account;
|
||||||
@ -56,36 +54,17 @@ class ReportController extends BaseController
|
|||||||
$action = Input::get('action');
|
$action = Input::get('action');
|
||||||
|
|
||||||
if (Input::all()) {
|
if (Input::all()) {
|
||||||
$groupBy = Input::get('group_by');
|
|
||||||
$chartType = Input::get('chart_type');
|
|
||||||
$reportType = Input::get('report_type');
|
$reportType = Input::get('report_type');
|
||||||
$dateField = Input::get('date_field');
|
$dateField = Input::get('date_field');
|
||||||
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
|
$startDate = Utils::toSqlDate(Input::get('start_date'), false);
|
||||||
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
|
$endDate = Utils::toSqlDate(Input::get('end_date'), false);
|
||||||
$enableReport = boolval(Input::get('enable_report'));
|
|
||||||
$enableChart = boolval(Input::get('enable_chart'));
|
|
||||||
} else {
|
} else {
|
||||||
$groupBy = 'MONTH';
|
|
||||||
$chartType = 'Bar';
|
|
||||||
$reportType = ENTITY_INVOICE;
|
$reportType = ENTITY_INVOICE;
|
||||||
$dateField = FILTER_INVOICE_DATE;
|
$dateField = FILTER_INVOICE_DATE;
|
||||||
$startDate = Utils::today(false)->modify('-3 month');
|
$startDate = Utils::today(false)->modify('-3 month');
|
||||||
$endDate = Utils::today(false);
|
$endDate = Utils::today(false);
|
||||||
$enableReport = true;
|
|
||||||
$enableChart = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$dateTypes = [
|
|
||||||
'DAYOFYEAR' => 'Daily',
|
|
||||||
'WEEK' => 'Weekly',
|
|
||||||
'MONTH' => 'Monthly',
|
|
||||||
];
|
|
||||||
|
|
||||||
$chartTypes = [
|
|
||||||
'Bar' => 'Bar',
|
|
||||||
'Line' => 'Line',
|
|
||||||
];
|
|
||||||
|
|
||||||
$reportTypes = [
|
$reportTypes = [
|
||||||
ENTITY_CLIENT => trans('texts.client'),
|
ENTITY_CLIENT => trans('texts.client'),
|
||||||
ENTITY_INVOICE => trans('texts.invoice'),
|
ENTITY_INVOICE => trans('texts.invoice'),
|
||||||
@ -96,148 +75,29 @@ class ReportController extends BaseController
|
|||||||
];
|
];
|
||||||
|
|
||||||
$params = [
|
$params = [
|
||||||
'dateTypes' => $dateTypes,
|
|
||||||
'chartTypes' => $chartTypes,
|
|
||||||
'chartType' => $chartType,
|
|
||||||
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
'startDate' => $startDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||||
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
|
'endDate' => $endDate->format(Session::get(SESSION_DATE_FORMAT)),
|
||||||
'groupBy' => $groupBy,
|
|
||||||
'reportTypes' => $reportTypes,
|
'reportTypes' => $reportTypes,
|
||||||
'reportType' => $reportType,
|
'reportType' => $reportType,
|
||||||
'enableChart' => $enableChart,
|
|
||||||
'enableReport' => $enableReport,
|
|
||||||
'title' => trans('texts.charts_and_reports'),
|
'title' => trans('texts.charts_and_reports'),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
if (Auth::user()->account->hasFeature(FEATURE_REPORTS)) {
|
||||||
if ($enableReport) {
|
$isExport = $action == 'export';
|
||||||
$isExport = $action == 'export';
|
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
|
||||||
$params = array_merge($params, self::generateReport($reportType, $startDate, $endDate, $dateField, $isExport));
|
|
||||||
|
|
||||||
if ($isExport) {
|
if ($isExport) {
|
||||||
self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
|
self::export($reportType, $params['displayData'], $params['columns'], $params['reportTotals']);
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($enableChart) {
|
|
||||||
$params = array_merge($params, self::generateChart($groupBy, $startDate, $endDate));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$params['columns'] = [];
|
$params['columns'] = [];
|
||||||
$params['displayData'] = [];
|
$params['displayData'] = [];
|
||||||
$params['reportTotals'] = [];
|
$params['reportTotals'] = [];
|
||||||
$params['labels'] = [];
|
|
||||||
$params['datasets'] = [];
|
|
||||||
$params['scaleStepWidth'] = 100;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return View::make('reports.chart_builder', $params);
|
return View::make('reports.chart_builder', $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param $groupBy
|
|
||||||
* @param $startDate
|
|
||||||
* @param $endDate
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
private function generateChart($groupBy, $startDate, $endDate)
|
|
||||||
{
|
|
||||||
$width = 10;
|
|
||||||
$datasets = [];
|
|
||||||
$labels = [];
|
|
||||||
$maxTotals = 0;
|
|
||||||
|
|
||||||
foreach ([ENTITY_INVOICE, ENTITY_PAYMENT, ENTITY_CREDIT] as $entityType) {
|
|
||||||
// SQLite does not support the YEAR(), MONTH(), WEEK() and similar functions.
|
|
||||||
// Let's see if SQLite is being used.
|
|
||||||
if (Config::get('database.connections.'.Config::get('database.default').'.driver') == 'sqlite') {
|
|
||||||
// Replace the unsupported function with it's date format counterpart
|
|
||||||
switch ($groupBy) {
|
|
||||||
case 'MONTH':
|
|
||||||
$dateFormat = '%m'; // returns 01-12
|
|
||||||
break;
|
|
||||||
case 'WEEK':
|
|
||||||
$dateFormat = '%W'; // returns 00-53
|
|
||||||
break;
|
|
||||||
case 'DAYOFYEAR':
|
|
||||||
$dateFormat = '%j'; // returns 001-366
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
$dateFormat = '%m'; // MONTH by default
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Concatenate the year and the chosen timeframe (Month, Week or Day)
|
|
||||||
$timeframe = 'strftime("%Y", '.$entityType.'_date) || strftime("'.$dateFormat.'", '.$entityType.'_date)';
|
|
||||||
} else {
|
|
||||||
// Supported by Laravel's other DBMS drivers (MySQL, MSSQL and PostgreSQL)
|
|
||||||
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
|
|
||||||
}
|
|
||||||
|
|
||||||
$records = DB::table($entityType.'s')
|
|
||||||
->select(DB::raw('sum('.$entityType.'s.amount) as total, '.$timeframe.' as '.$groupBy))
|
|
||||||
->join('clients', 'clients.id', '=', $entityType.'s.client_id')
|
|
||||||
->where('clients.is_deleted', '=', false)
|
|
||||||
->where($entityType.'s.account_id', '=', Auth::user()->account_id)
|
|
||||||
->where($entityType.'s.is_deleted', '=', false)
|
|
||||||
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
|
|
||||||
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
|
|
||||||
->groupBy($groupBy);
|
|
||||||
|
|
||||||
if ($entityType == ENTITY_INVOICE) {
|
|
||||||
$records->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
|
|
||||||
->where('is_recurring', '=', false);
|
|
||||||
} elseif ($entityType == ENTITY_PAYMENT) {
|
|
||||||
$records->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
|
||||||
->where('invoices.is_deleted', '=', false)
|
|
||||||
->whereNotIn('payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$totals = $records->lists('total');
|
|
||||||
$dates = $records->lists($groupBy);
|
|
||||||
$data = array_combine($dates, $totals);
|
|
||||||
|
|
||||||
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
|
|
||||||
$endDate->modify('+1 '.$padding);
|
|
||||||
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
|
|
||||||
$period = new DatePeriod($startDate, $interval, $endDate);
|
|
||||||
$endDate->modify('-1 '.$padding);
|
|
||||||
|
|
||||||
$totals = [];
|
|
||||||
|
|
||||||
foreach ($period as $d) {
|
|
||||||
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
|
|
||||||
// MySQL returns 1-366 for DAYOFYEAR, whereas PHP returns 0-365
|
|
||||||
$date = $groupBy == 'DAYOFYEAR' ? $d->format('Y').($d->format($dateFormat) + 1) : $d->format('Y'.$dateFormat);
|
|
||||||
$totals[] = isset($data[$date]) ? $data[$date] : 0;
|
|
||||||
|
|
||||||
if ($entityType == ENTITY_INVOICE) {
|
|
||||||
$labelFormat = $groupBy == 'DAYOFYEAR' ? 'j' : ($groupBy == 'WEEK' ? 'W' : 'F');
|
|
||||||
$label = $d->format($labelFormat);
|
|
||||||
$labels[] = $label;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$max = max($totals);
|
|
||||||
|
|
||||||
if ($max > 0) {
|
|
||||||
$datasets[] = [
|
|
||||||
'totals' => $totals,
|
|
||||||
'colors' => $entityType == ENTITY_INVOICE ? '78,205,196' : ($entityType == ENTITY_CREDIT ? '199,244,100' : '255,107,107'),
|
|
||||||
];
|
|
||||||
$maxTotals = max($max, $maxTotals);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$width = (ceil($maxTotals / 100) * 100) / 10;
|
|
||||||
$width = max($width, 10);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'datasets' => $datasets,
|
|
||||||
'scaleStepWidth' => $width,
|
|
||||||
'labels' => $labels,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $reportType
|
* @param $reportType
|
||||||
* @param $startDate
|
* @param $startDate
|
||||||
|
@ -18,8 +18,8 @@ class Kernel extends HttpKernel {
|
|||||||
'App\Http\Middleware\VerifyCsrfToken',
|
'App\Http\Middleware\VerifyCsrfToken',
|
||||||
'App\Http\Middleware\DuplicateSubmissionCheck',
|
'App\Http\Middleware\DuplicateSubmissionCheck',
|
||||||
'App\Http\Middleware\QueryLogging',
|
'App\Http\Middleware\QueryLogging',
|
||||||
'App\Http\Middleware\StartupCheck',
|
|
||||||
'App\Http\Middleware\SessionDataCheckMiddleware',
|
'App\Http\Middleware\SessionDataCheckMiddleware',
|
||||||
|
'App\Http\Middleware\StartupCheck',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,18 +14,16 @@ class SessionDataCheckMiddleware {
|
|||||||
* @param \Closure $next
|
* @param \Closure $next
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
public function handle($request, Closure $next) {
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
$bag = Session::getMetadataBag();
|
$bag = Session::getMetadataBag();
|
||||||
|
$max = env('IDLE_TIMEOUT_MINUTES', 6 * 60) * 60; // minute to second conversion
|
||||||
|
$elapsed = time() - $bag->getLastUsed();
|
||||||
|
|
||||||
$max = config('session.lifetime') * 60; // minute to second conversion
|
if ( ! $bag || $elapsed > $max) {
|
||||||
|
$request->session()->flush();
|
||||||
if (($bag && $max < (time() - $bag->getLastUsed()))) {
|
Auth::logout();
|
||||||
|
$request->session()->flash('warning', trans('texts.inactive_logout'));
|
||||||
$request->session()->flush(); // remove all the session data
|
|
||||||
|
|
||||||
Auth::logout(); // logout user
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
||||||
|
@ -123,6 +123,7 @@ if (Utils::isReseller()) {
|
|||||||
|
|
||||||
Route::group(['middleware' => 'auth:user'], function() {
|
Route::group(['middleware' => 'auth:user'], function() {
|
||||||
Route::get('dashboard', 'DashboardController@index');
|
Route::get('dashboard', 'DashboardController@index');
|
||||||
|
Route::get('dashboard_chart_data/{group_by}/{start_date}/{end_date}/{currency_id}/{include_expenses}', 'DashboardController@chartData');
|
||||||
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
|
Route::get('view_archive/{entity_type}/{visible}', 'AccountController@setTrashVisible');
|
||||||
Route::get('hide_message', 'HomeController@hideMessage');
|
Route::get('hide_message', 'HomeController@hideMessage');
|
||||||
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
|
Route::get('force_inline_pdf', 'UserController@forcePDFJS');
|
||||||
@ -238,8 +239,8 @@ Route::group([
|
|||||||
Route::get('settings/email_preview', 'AccountController@previewEmail');
|
Route::get('settings/email_preview', 'AccountController@previewEmail');
|
||||||
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
|
Route::get('company/{section}/{subSection?}', 'AccountController@redirectLegacy');
|
||||||
Route::get('settings/data_visualizations', 'ReportController@d3');
|
Route::get('settings/data_visualizations', 'ReportController@d3');
|
||||||
Route::get('settings/charts_and_reports', 'ReportController@showReports');
|
Route::get('settings/reports', 'ReportController@showReports');
|
||||||
Route::post('settings/charts_and_reports', 'ReportController@showReports');
|
Route::post('settings/reports', 'ReportController@showReports');
|
||||||
|
|
||||||
Route::post('settings/change_plan', 'AccountController@changePlan');
|
Route::post('settings/change_plan', 'AccountController@changePlan');
|
||||||
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
|
Route::post('settings/cancel_account', 'AccountController@cancelAccount');
|
||||||
@ -411,7 +412,7 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
|
define('ACCOUNT_INVOICE_DESIGN', 'invoice_design');
|
||||||
define('ACCOUNT_CLIENT_PORTAL', 'client_portal');
|
define('ACCOUNT_CLIENT_PORTAL', 'client_portal');
|
||||||
define('ACCOUNT_EMAIL_SETTINGS', 'email_settings');
|
define('ACCOUNT_EMAIL_SETTINGS', 'email_settings');
|
||||||
define('ACCOUNT_CHARTS_AND_REPORTS', 'charts_and_reports');
|
define('ACCOUNT_REPORTS', 'reports');
|
||||||
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
|
define('ACCOUNT_USER_MANAGEMENT', 'user_management');
|
||||||
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
|
define('ACCOUNT_DATA_VISUALIZATIONS', 'data_visualizations');
|
||||||
define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders');
|
define('ACCOUNT_TEMPLATES_AND_REMINDERS', 'templates_and_reminders');
|
||||||
|
@ -96,7 +96,7 @@ class Account extends Eloquent
|
|||||||
ACCOUNT_TEMPLATES_AND_REMINDERS,
|
ACCOUNT_TEMPLATES_AND_REMINDERS,
|
||||||
ACCOUNT_BANKS,
|
ACCOUNT_BANKS,
|
||||||
ACCOUNT_CLIENT_PORTAL,
|
ACCOUNT_CLIENT_PORTAL,
|
||||||
ACCOUNT_CHARTS_AND_REPORTS,
|
ACCOUNT_REPORTS,
|
||||||
ACCOUNT_DATA_VISUALIZATIONS,
|
ACCOUNT_DATA_VISUALIZATIONS,
|
||||||
ACCOUNT_API_TOKENS,
|
ACCOUNT_API_TOKENS,
|
||||||
ACCOUNT_USER_MANAGEMENT,
|
ACCOUNT_USER_MANAGEMENT,
|
||||||
@ -401,11 +401,7 @@ class Account extends Eloquent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function getDate($date = 'now')
|
||||||
* @param string $date
|
|
||||||
* @return DateTime|null|string
|
|
||||||
*/
|
|
||||||
public function getDateTime($date = 'now')
|
|
||||||
{
|
{
|
||||||
if ( ! $date) {
|
if ( ! $date) {
|
||||||
return null;
|
return null;
|
||||||
@ -413,6 +409,16 @@ class Account extends Eloquent
|
|||||||
$date = new \DateTime($date);
|
$date = new \DateTime($date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $date
|
||||||
|
* @return DateTime|null|string
|
||||||
|
*/
|
||||||
|
public function getDateTime($date = 'now')
|
||||||
|
{
|
||||||
|
$date = $this->getDate($date);
|
||||||
$date->setTimeZone(new \DateTimeZone($this->getTimezone()));
|
$date->setTimeZone(new \DateTimeZone($this->getTimezone()));
|
||||||
|
|
||||||
return $date;
|
return $date;
|
||||||
@ -469,7 +475,7 @@ class Account extends Eloquent
|
|||||||
*/
|
*/
|
||||||
public function formatDate($date)
|
public function formatDate($date)
|
||||||
{
|
{
|
||||||
$date = $this->getDateTime($date);
|
$date = $this->getDate($date);
|
||||||
|
|
||||||
if ( ! $date) {
|
if ( ! $date) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -156,6 +156,15 @@ class Task extends EntityModel
|
|||||||
{
|
{
|
||||||
return '#' . $this->public_id;
|
return '#' . $this->public_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getDisplayName()
|
||||||
|
{
|
||||||
|
if ($this->description) {
|
||||||
|
return mb_strimwidth($this->description, 0, 16, "...");
|
||||||
|
}
|
||||||
|
|
||||||
|
return '#' . $this->public_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -597,8 +597,11 @@ class BasePaymentDriver
|
|||||||
$term = strtolower($matches[2]);
|
$term = strtolower($matches[2]);
|
||||||
$price = $invoice_item->cost;
|
$price = $invoice_item->cost;
|
||||||
if ($plan == PLAN_ENTERPRISE) {
|
if ($plan == PLAN_ENTERPRISE) {
|
||||||
preg_match('/###[\d] [\w]* (\d*)/', $invoice_item->notes, $matches);
|
if (count($matches)) {
|
||||||
$numUsers = $matches[1];
|
$numUsers = $matches[1];
|
||||||
|
} else {
|
||||||
|
$numUsers = 5;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$numUsers = 1;
|
$numUsers = 1;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,148 @@
|
|||||||
<?php namespace App\Ninja\Repositories;
|
<?php namespace App\Ninja\Repositories;
|
||||||
|
|
||||||
|
use stdClass;
|
||||||
use DB;
|
use DB;
|
||||||
use App\Models\Activity;
|
use App\Models\Activity;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\Task;
|
||||||
|
use DateInterval;
|
||||||
|
use DatePeriod;
|
||||||
|
|
||||||
class DashboardRepository
|
class DashboardRepository
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @param $groupBy
|
||||||
|
* @param $startDate
|
||||||
|
* @param $endDate
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function chartData($account, $groupBy, $startDate, $endDate, $currencyId, $includeExpenses)
|
||||||
|
{
|
||||||
|
$accountId = $account->id;
|
||||||
|
$startDate = date_create($startDate);
|
||||||
|
$endDate = date_create($endDate);
|
||||||
|
$groupBy = strtoupper($groupBy);
|
||||||
|
if ($groupBy == 'DAY') {
|
||||||
|
$groupBy = 'DAYOFYEAR';
|
||||||
|
}
|
||||||
|
|
||||||
|
$datasets = [];
|
||||||
|
$labels = [];
|
||||||
|
$totals = new stdClass;
|
||||||
|
|
||||||
|
$entitTypes = [ENTITY_INVOICE, ENTITY_PAYMENT];
|
||||||
|
if ($includeExpenses) {
|
||||||
|
$entitTypes[] = ENTITY_EXPENSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($entitTypes as $entityType) {
|
||||||
|
|
||||||
|
$data = [];
|
||||||
|
$count = 0;
|
||||||
|
$records = $this->rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId);
|
||||||
|
|
||||||
|
array_map(function ($item) use (&$data, &$count, $groupBy) {
|
||||||
|
$data[$item->$groupBy] = $item->total;
|
||||||
|
$count += $item->count;
|
||||||
|
}, $records->get());
|
||||||
|
|
||||||
|
$padding = $groupBy == 'DAYOFYEAR' ? 'day' : ($groupBy == 'WEEK' ? 'week' : 'month');
|
||||||
|
$endDate->modify('+1 '.$padding);
|
||||||
|
$interval = new DateInterval('P1'.substr($groupBy, 0, 1));
|
||||||
|
$period = new DatePeriod($startDate, $interval, $endDate);
|
||||||
|
$endDate->modify('-1 '.$padding);
|
||||||
|
$records = [];
|
||||||
|
|
||||||
|
foreach ($period as $d) {
|
||||||
|
$dateFormat = $groupBy == 'DAYOFYEAR' ? 'z' : ($groupBy == 'WEEK' ? 'W' : 'n');
|
||||||
|
// MySQL returns 1-366 for DAYOFYEAR, whereas PHP returns 0-365
|
||||||
|
$date = $groupBy == 'DAYOFYEAR' ? $d->format('Y').($d->format($dateFormat) + 1) : $d->format('Y'.$dateFormat);
|
||||||
|
$records[] = isset($data[$date]) ? $data[$date] : 0;
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_INVOICE) {
|
||||||
|
$labels[] = $d->format('r');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_INVOICE) {
|
||||||
|
$color = '51,122,183';
|
||||||
|
} elseif ($entityType == ENTITY_PAYMENT) {
|
||||||
|
$color = '54,193,87';
|
||||||
|
} elseif ($entityType == ENTITY_EXPENSE) {
|
||||||
|
$color = '128,128,128';
|
||||||
|
}
|
||||||
|
|
||||||
|
$record = new stdClass;
|
||||||
|
$record->data = $records;
|
||||||
|
$record->label = trans("texts.{$entityType}s");
|
||||||
|
$record->lineTension = 0;
|
||||||
|
$record->borderWidth = 4;
|
||||||
|
$record->borderColor = "rgba({$color}, 1)";
|
||||||
|
$record->backgroundColor = "rgba({$color}, 0.05)";
|
||||||
|
$datasets[] = $record;
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_INVOICE) {
|
||||||
|
$totals->invoices = array_sum($data);
|
||||||
|
$totals->average = $count ? round($totals->invoices / $count, 2) : 0;
|
||||||
|
} elseif ($entityType == ENTITY_PAYMENT) {
|
||||||
|
$totals->revenue = array_sum($data);
|
||||||
|
$totals->balance = $totals->invoices - $totals->revenue;
|
||||||
|
} elseif ($entityType == ENTITY_EXPENSE) {
|
||||||
|
//$totals->profit = $totals->revenue - array_sum($data);
|
||||||
|
$totals->expenses = array_sum($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = new stdClass;
|
||||||
|
$data->labels = $labels;
|
||||||
|
$data->datasets = $datasets;
|
||||||
|
|
||||||
|
$response = new stdClass;
|
||||||
|
$response->data = $data;
|
||||||
|
$response->totals = $totals;
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rawChartData($entityType, $account, $groupBy, $startDate, $endDate, $currencyId)
|
||||||
|
{
|
||||||
|
$accountId = $account->id;
|
||||||
|
$currencyId = intval($currencyId);
|
||||||
|
$timeframe = 'concat(YEAR('.$entityType.'_date), '.$groupBy.'('.$entityType.'_date))';
|
||||||
|
|
||||||
|
$records = DB::table($entityType.'s')
|
||||||
|
->join('clients', 'clients.id', '=', $entityType.'s.client_id')
|
||||||
|
->where('clients.is_deleted', '=', false)
|
||||||
|
->where($entityType.'s.account_id', '=', $accountId)
|
||||||
|
->where($entityType.'s.is_deleted', '=', false)
|
||||||
|
->where($entityType.'s.'.$entityType.'_date', '>=', $startDate->format('Y-m-d'))
|
||||||
|
->where($entityType.'s.'.$entityType.'_date', '<=', $endDate->format('Y-m-d'))
|
||||||
|
->groupBy($groupBy);
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_EXPENSE) {
|
||||||
|
$records->where('expenses.expense_currency_id', '=', $currencyId);
|
||||||
|
} elseif ($currencyId == $account->getCurrencyId()) {
|
||||||
|
$records->whereRaw("(clients.currency_id = {$currencyId} or coalesce(clients.currency_id, 0) = 0)");
|
||||||
|
} else {
|
||||||
|
$records->where('clients.currency_id', '=', $currencyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($entityType == ENTITY_INVOICE) {
|
||||||
|
$records->select(DB::raw('sum(invoices.amount) as total, count(invoices.id) as count, '.$timeframe.' as '.$groupBy))
|
||||||
|
->where('invoice_type_id', '=', INVOICE_TYPE_STANDARD)
|
||||||
|
->where('is_recurring', '=', false);
|
||||||
|
} elseif ($entityType == ENTITY_PAYMENT) {
|
||||||
|
$records->select(DB::raw('sum(payments.amount - payments.refunded) as total, count(payments.id) as count, '.$timeframe.' as '.$groupBy))
|
||||||
|
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||||
|
->where('invoices.is_deleted', '=', false)
|
||||||
|
->whereNotIn('payment_status_id', [PAYMENT_STATUS_VOIDED, PAYMENT_STATUS_FAILED]);
|
||||||
|
} elseif ($entityType == ENTITY_EXPENSE) {
|
||||||
|
$records->select(DB::raw('sum(expenses.amount) as total, count(expenses.id) as count, '.$timeframe.' as '.$groupBy));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $records;
|
||||||
|
}
|
||||||
|
|
||||||
public function totals($accountId, $userId, $viewAll)
|
public function totals($accountId, $userId, $viewAll)
|
||||||
{
|
{
|
||||||
// total_income, billed_clients, invoice_sent and active_clients
|
// total_income, billed_clients, invoice_sent and active_clients
|
||||||
@ -193,4 +331,33 @@ class DashboardRepository
|
|||||||
->take(50)
|
->take(50)
|
||||||
->get();
|
->get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function expenses($accountId, $userId, $viewAll)
|
||||||
|
{
|
||||||
|
$select = DB::raw(
|
||||||
|
'SUM('.DB::getQueryGrammar()->wrap('expenses.amount', true).') as value,'
|
||||||
|
.DB::getQueryGrammar()->wrap('expenses.expense_currency_id', true).' as currency_id'
|
||||||
|
);
|
||||||
|
$paidToDate = DB::table('accounts')
|
||||||
|
->select($select)
|
||||||
|
->leftJoin('expenses', 'accounts.id', '=', 'expenses.account_id')
|
||||||
|
->where('accounts.id', '=', $accountId)
|
||||||
|
->where('expenses.is_deleted', '=', false);
|
||||||
|
|
||||||
|
if (!$viewAll){
|
||||||
|
$paidToDate = $paidToDate->where('expenses.user_id', '=', $userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $paidToDate->groupBy('accounts.id')
|
||||||
|
->groupBy('expenses.expense_currency_id')
|
||||||
|
->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tasks($accountId, $userId, $viewAll)
|
||||||
|
{
|
||||||
|
return Task::scope()
|
||||||
|
->withArchived()
|
||||||
|
->whereIsRunning(true)
|
||||||
|
->get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,8 @@
|
|||||||
"stacktrace-js": "~1.0.1",
|
"stacktrace-js": "~1.0.1",
|
||||||
"fuse.js": "~2.0.2",
|
"fuse.js": "~2.0.2",
|
||||||
"dropzone": "~4.3.0",
|
"dropzone": "~4.3.0",
|
||||||
"sweetalert": "~1.1.3"
|
"sweetalert": "~1.1.3",
|
||||||
|
"bootstrap-daterangepicker": "~2.1.24"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"jquery": "~1.11"
|
"jquery": "~1.11"
|
||||||
|
@ -29,7 +29,7 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'lifetime' => env('SESSION_LIFETIME', 120),
|
'lifetime' => env('SESSION_LIFETIME', (60 * 8)),
|
||||||
|
|
||||||
'expire_on_close' => false,
|
'expire_on_close' => false,
|
||||||
|
|
||||||
|
@ -58,6 +58,11 @@ elixir(function(mix) {
|
|||||||
'fonts.css'
|
'fonts.css'
|
||||||
], 'public/css/built.css');
|
], 'public/css/built.css');
|
||||||
|
|
||||||
|
mix.styles([
|
||||||
|
bowerDir + '/bootstrap-daterangepicker/daterangepicker.css'
|
||||||
|
], 'public/css/daterangepicker.css');
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JS configuration
|
* JS configuration
|
||||||
*/
|
*/
|
||||||
@ -71,6 +76,10 @@ elixir(function(mix) {
|
|||||||
'vfs.js'
|
'vfs.js'
|
||||||
], 'public/pdf.built.js');
|
], 'public/pdf.built.js');
|
||||||
|
|
||||||
|
mix.scripts([
|
||||||
|
bowerDir + '/bootstrap-daterangepicker/daterangepicker.js'
|
||||||
|
], 'public/js/daterangepicker.min.js');
|
||||||
|
|
||||||
mix.scripts([
|
mix.scripts([
|
||||||
bowerDir + '/jquery/dist/jquery.js',
|
bowerDir + '/jquery/dist/jquery.js',
|
||||||
bowerDir + '/jquery-ui/jquery-ui.js',
|
bowerDir + '/jquery-ui/jquery-ui.js',
|
||||||
|
2
public/css/built.css
vendored
2
public/css/built.css
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
public/css/daterangepicker.css
vendored
Normal file
2
public/css/daterangepicker.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/css/daterangepicker.css.map
Normal file
1
public/css/daterangepicker.css.map
Normal file
File diff suppressed because one or more lines are too long
6
public/js/Chart.min.js
vendored
6
public/js/Chart.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
public/js/daterangepicker.min.js
vendored
Normal file
3
public/js/daterangepicker.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/js/daterangepicker.min.js.map
Normal file
1
public/js/daterangepicker.min.js.map
Normal file
File diff suppressed because one or more lines are too long
4
resources/assets/css/sidebar.css
vendored
4
resources/assets/css/sidebar.css
vendored
@ -100,8 +100,8 @@
|
|||||||
|
|
||||||
#right-sidebar-wrapper .sidebar-nav li {
|
#right-sidebar-wrapper .sidebar-nav li {
|
||||||
text-indent: 8px;
|
text-indent: 8px;
|
||||||
font-size: 16px;
|
font-size: 15px;
|
||||||
line-height: 44px;
|
line-height: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#right-sidebar-wrapper .sidebar-nav li a.btn {
|
#right-sidebar-wrapper .sidebar-nav li a.btn {
|
||||||
|
11821
resources/assets/js/Chart.js
vendored
11821
resources/assets/js/Chart.js
vendored
File diff suppressed because it is too large
Load Diff
@ -2099,6 +2099,15 @@ $LANG = array(
|
|||||||
'facebook_and_twitter_help' => 'Follow our feeds to help support our project',
|
'facebook_and_twitter_help' => 'Follow our feeds to help support our project',
|
||||||
'reseller_text' => 'Note: the white-label license is intended for personal use, please email us at :email if you\'d like to resell our app.',
|
'reseller_text' => 'Note: the white-label license is intended for personal use, please email us at :email if you\'d like to resell our app.',
|
||||||
'unnamed_client' => 'Unnamed Client',
|
'unnamed_client' => 'Unnamed Client',
|
||||||
|
|
||||||
|
'day' => 'Day',
|
||||||
|
'week' => 'Week',
|
||||||
|
'month' => 'Month',
|
||||||
|
'inactive_logout' => 'You have been logged out due to inactivity',
|
||||||
|
'reports' => 'Reports',
|
||||||
|
'total_profit' => 'Total Profit',
|
||||||
|
'total_expenses' => 'Total Expenses',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return $LANG;
|
return $LANG;
|
||||||
|
@ -1,22 +1,214 @@
|
|||||||
@extends('header')
|
@extends('header')
|
||||||
|
|
||||||
|
@section('head')
|
||||||
|
@parent
|
||||||
|
|
||||||
|
@include('money_script')
|
||||||
|
|
||||||
|
<script src="{!! asset('js/Chart.min.js') !!}" type="text/javascript"></script>
|
||||||
|
<script src="{{ asset('js/daterangepicker.min.js') }}" type="text/javascript"></script>
|
||||||
|
<link href="{{ asset('css/daterangepicker.css') }}" rel="stylesheet" type="text/css"/>
|
||||||
|
|
||||||
|
@stop
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
|
||||||
|
@if (Auth::user()->hasPermission('view_all'))
|
||||||
|
function loadChart(data) {
|
||||||
|
var ctx = document.getElementById('chart-canvas').getContext('2d');
|
||||||
|
|
||||||
|
if (window.myChart) {
|
||||||
|
window.myChart.config.data = data;
|
||||||
|
window.myChart.config.options.scales.xAxes[0].time.unit = chartGropuBy.toLowerCase();
|
||||||
|
window.myChart.update();
|
||||||
|
} else {
|
||||||
|
$('#progress-div').hide();
|
||||||
|
$('#chart-canvas').fadeIn();
|
||||||
|
window.myChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: data,
|
||||||
|
options: {
|
||||||
|
tooltips: {
|
||||||
|
mode: 'x-axis',
|
||||||
|
titleFontSize: 15,
|
||||||
|
titleMarginBottom: 12,
|
||||||
|
bodyFontSize: 15,
|
||||||
|
bodySpacing: 10,
|
||||||
|
callbacks: {
|
||||||
|
title: function(item) {
|
||||||
|
return moment(item[0].xLabel).format("{{ $account->getMomentDateFormat() }}");
|
||||||
|
},
|
||||||
|
label: function(item, data) {
|
||||||
|
if (item.datasetIndex == 0) {
|
||||||
|
var label = " {{ trans('texts.invoices') }}: ";
|
||||||
|
} else if (item.datasetIndex == 1) {
|
||||||
|
var label = " {{ trans('texts.payments') }}: ";
|
||||||
|
} else if (item.datasetIndex == 2) {
|
||||||
|
var label = " {{ trans('texts.expenses') }}: ";
|
||||||
|
}
|
||||||
|
|
||||||
|
return label + formatMoney(item.yLabel, chartCurrencyId, account.country_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
display: false,
|
||||||
|
fontSize: 18,
|
||||||
|
text: '{{ trans('texts.total_revenue') }}'
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
xAxes: [{
|
||||||
|
type: 'time',
|
||||||
|
time: {
|
||||||
|
unit: 'day',
|
||||||
|
},
|
||||||
|
gridLines: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: true,
|
||||||
|
callback: function(label, index, labels) {
|
||||||
|
return formatMoney(label, chartCurrencyId, account.country_id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var account = {!! $account !!};
|
||||||
|
var chartStartDate = moment().subtract(29, 'days');
|
||||||
|
var chartEndDate = moment();
|
||||||
|
var chartGropuBy = 'day';
|
||||||
|
var chartCurrencyId = {{ $account->getCurrencyId() }};
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
|
||||||
|
// Initialize date range selector
|
||||||
|
|
||||||
|
function cb(start, end) {
|
||||||
|
$('#reportrange span').html(start.format('{{ $account->getMomentDateFormat() }}') + ' - ' + end.format('{{ $account->getMomentDateFormat() }}'));
|
||||||
|
chartStartDate = start;
|
||||||
|
chartEndDate = end;
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#reportrange').daterangepicker({
|
||||||
|
locale: {
|
||||||
|
"format": "{{ $account->getMomentDateFormat() }}",
|
||||||
|
},
|
||||||
|
startDate: chartStartDate,
|
||||||
|
endDate: chartEndDate,
|
||||||
|
linkedCalendars: false,
|
||||||
|
ranges: {
|
||||||
|
'Last 7 Days': [moment().subtract(6, 'days'), moment()],
|
||||||
|
'Last 30 Days': [moment().subtract(29, 'days'), moment()],
|
||||||
|
'This Month': [moment().startOf('month'), moment().endOf('month')],
|
||||||
|
'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')]
|
||||||
|
}
|
||||||
|
}, cb);
|
||||||
|
|
||||||
|
cb(chartStartDate, chartEndDate);
|
||||||
|
|
||||||
|
$("#currency-btn-group > .btn").click(function(){
|
||||||
|
$(this).addClass("active").siblings().removeClass("active");
|
||||||
|
chartCurrencyId = currencyMap[$(this).text()].id;
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#group-btn-group > .btn").click(function(){
|
||||||
|
$(this).addClass("active").siblings().removeClass("active");
|
||||||
|
chartGropuBy = $(this).text();
|
||||||
|
loadData();
|
||||||
|
});
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
var includeExpenses = "{{ count($expenses) ? 'true' : 'false' }}";
|
||||||
|
var url = "{!! url('/dashboard_chart_data') !!}/" + chartGropuBy + '/' + chartStartDate.format('YYYY-MM-DD') + '/' + chartEndDate.format('YYYY-MM-DD') + '/' + chartCurrencyId + '/' + includeExpenses;
|
||||||
|
$.get(url, function(response) {
|
||||||
|
response = JSON.parse(response);
|
||||||
|
loadChart(response.data);
|
||||||
|
|
||||||
|
var totals = response.totals;
|
||||||
|
$('.revenue-div').text(formatMoney(totals.revenue, chartCurrencyId, account.country_id));
|
||||||
|
$('.outstanding-div').text(formatMoney(totals.balance, chartCurrencyId, account.country_id));
|
||||||
|
$('.expenses-div').text(formatMoney(totals.expenses, chartCurrencyId, account.country_id));
|
||||||
|
$('.average-div').text(formatMoney(totals.average, chartCurrencyId, account.country_id));
|
||||||
|
|
||||||
|
$('.currency').hide();
|
||||||
|
$('.currency_' + chartCurrencyId).show();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
@else
|
||||||
|
$(function() {
|
||||||
|
$('.currency').show();
|
||||||
|
})
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-2">
|
||||||
|
<ol class="breadcrumb"><li class='active'>{{ trans('texts.dashboard') }}</li></ol>
|
||||||
|
</div>
|
||||||
|
@if (count($tasks))
|
||||||
|
<div class="col-md-2" style="padding-top:6px">
|
||||||
|
@foreach ($tasks as $task)
|
||||||
|
{!! Button::primary($task->present()->titledName)->small()->asLinkTo($task->present()->url) !!}
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
@else
|
||||||
|
<div class="col-md-10">
|
||||||
|
@endif
|
||||||
|
@if (Auth::user()->hasPermission('view_all'))
|
||||||
|
<div class="pull-right">
|
||||||
|
@if (count($currencies) > 1)
|
||||||
|
<div id="currency-btn-group" class="btn-group" role="group" style="border: 1px solid #ccc;">
|
||||||
|
@foreach ($currencies as $key => $val)
|
||||||
|
<button type="button" class="btn btn-normal {{ array_values($currencies)[0] == $val ? 'active' : '' }}"
|
||||||
|
style="font-weight:normal !important;background-color:white">{{ $val }}</button>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<div id="group-btn-group" class="btn-group" role="group" style="border: 1px solid #ccc; margin-left:18px">
|
||||||
|
<button type="button" class="btn btn-normal active" style="font-weight:normal !important;background-color:white">{{ trans('texts.day') }}</button>
|
||||||
|
<button type="button" class="btn btn-normal" style="font-weight:normal !important;background-color:white">{{ trans('texts.week') }}</button>
|
||||||
|
<button type="button" class="btn btn-normal" style="font-weight:normal !important;background-color:white">{{ trans('texts.month') }}</button>
|
||||||
|
</div>
|
||||||
|
<div id="reportrange" class="pull-right" style="background: #fff; cursor: pointer; padding: 9px 14px; border: 1px solid #ccc; margin-top: 0px; margin-left:18px">
|
||||||
|
<i class="glyphicon glyphicon-calendar fa fa-calendar"></i>
|
||||||
|
<span></span> <b class="caret"></b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<img src="{{ asset('images/totalinvoices.png') }}"
|
|
||||||
class="in-image" style="float:left" width="80" height="80"/>
|
|
||||||
<div style="overflow:hidden">
|
<div style="overflow:hidden">
|
||||||
<div class="in-thin">
|
<div class="in-thin">
|
||||||
{{ trans('texts.total_revenue') }}
|
{{ trans('texts.total_revenue') }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="revenue-div in-bold pull-right" style="color:#337ab7">
|
||||||
|
</div>
|
||||||
<div class="in-bold">
|
<div class="in-bold">
|
||||||
@if (count($paidToDate))
|
@if (count($paidToDate))
|
||||||
@foreach ($paidToDate as $item)
|
@foreach ($paidToDate as $item)
|
||||||
{{ Utils::formatMoney($item->value, $item->currency_id) }}<br/>
|
<div class="currency currency_{{ $item->currency_id ?: $account->getCurrencyId() }}" style="display:none">
|
||||||
|
{{ Utils::formatMoney($item->value, $item->currency_id) }}
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@else
|
@else
|
||||||
{{ Utils::formatMoney(0) }}
|
{{ Utils::formatMoney(0) }}
|
||||||
@ -29,21 +221,38 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<img src="{{ asset('images/clients.png') }}"
|
|
||||||
class="in-image" style="float:left" width="80" height="80"/>
|
|
||||||
<div style="overflow:hidden">
|
<div style="overflow:hidden">
|
||||||
<div class="in-thin">
|
@if (count($expenses))
|
||||||
{{ trans('texts.average_invoice') }}
|
<div class="in-thin">
|
||||||
</div>
|
{{ trans('texts.total_expenses') }}
|
||||||
<div class="in-bold">
|
</div>
|
||||||
@if (count($averageInvoice))
|
<div class="expenses-div in-bold pull-right" style="color:#337ab7">
|
||||||
@foreach ($averageInvoice as $item)
|
</div>
|
||||||
{{ Utils::formatMoney($item->invoice_avg, $item->currency_id) }}<br/>
|
<div class="in-bold">
|
||||||
|
@foreach ($expenses as $item)
|
||||||
|
<div class="currency currency_{{ $item->currency_id ?: $account->getCurrencyId() }}" style="display:none">
|
||||||
|
{{ Utils::formatMoney($item->value, $item->currency_id) }}<br/>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@else
|
</div>
|
||||||
{{ Utils::formatMoney(0) }}
|
@else
|
||||||
@endif
|
<div class="in-thin">
|
||||||
</div>
|
{{ trans('texts.average_invoice') }}
|
||||||
|
</div>
|
||||||
|
<div class="average-div in-bold pull-right" style="color:#337ab7">
|
||||||
|
</div>
|
||||||
|
<div class="in-bold">
|
||||||
|
@if (count($averageInvoice))
|
||||||
|
@foreach ($averageInvoice as $item)
|
||||||
|
<div class="currency currency_{{ $item->currency_id ?: $account->getCurrencyId() }}" style="display:none">
|
||||||
|
{{ Utils::formatMoney($item->invoice_avg, $item->currency_id) }}<br/>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
@else
|
||||||
|
{{ Utils::formatMoney(0) }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -51,16 +260,18 @@
|
|||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<img src="{{ asset('images/totalincome.png') }}"
|
|
||||||
class="in-image" style="float:left" width="80" height="80"/>
|
|
||||||
<div style="overflow:hidden">
|
<div style="overflow:hidden">
|
||||||
<div class="in-thin">
|
<div class="in-thin">
|
||||||
{{ trans('texts.outstanding') }}
|
{{ trans('texts.outstanding') }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="outstanding-div in-bold pull-right" style="color:#337ab7">
|
||||||
|
</div>
|
||||||
<div class="in-bold">
|
<div class="in-bold">
|
||||||
@if (count($balances))
|
@if (count($balances))
|
||||||
@foreach ($balances as $item)
|
@foreach ($balances as $item)
|
||||||
{{ Utils::formatMoney($item->value, $item->currency_id) }}<br/>
|
<div class="currency currency_{{ $item->currency_id ?: $account->getCurrencyId() }}" style="display:none">
|
||||||
|
{{ Utils::formatMoney($item->value, $item->currency_id) }}<br/>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
@else
|
@else
|
||||||
{{ Utils::formatMoney(0) }}
|
{{ Utils::formatMoney(0) }}
|
||||||
@ -72,13 +283,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (Auth::user()->hasPermission('view_all'))
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div id="progress-div" class="progress">
|
||||||
|
<div class="progress-bar progress-bar-striped active" role="progressbar"
|
||||||
|
aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
|
||||||
|
</div>
|
||||||
|
<canvas id="chart-canvas" height="70px" style="background-color:white;padding:20px;display:none"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<p> </p>
|
<p> </p>
|
||||||
|
@endif
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default dashboard" style="height:320px">
|
<div class="panel panel-default dashboard" style="height:320px">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading" style="background-color:#286090 !important">
|
||||||
<h3 class="panel-title in-bold-white">
|
<h3 class="panel-title in-bold-white">
|
||||||
<i class="glyphicon glyphicon-exclamation-sign"></i> {{ trans('texts.activity') }}
|
<i class="glyphicon glyphicon-exclamation-sign"></i> {{ trans('texts.activity') }}
|
||||||
@if ($invoicesSent)
|
@if ($invoicesSent)
|
||||||
@ -103,6 +324,17 @@
|
|||||||
<div class="panel panel-default dashboard" style="height:320px;">
|
<div class="panel panel-default dashboard" style="height:320px;">
|
||||||
<div class="panel-heading" style="margin:0; background-color: #f5f5f5 !important;">
|
<div class="panel-heading" style="margin:0; background-color: #f5f5f5 !important;">
|
||||||
<h3 class="panel-title" style="color: black !important">
|
<h3 class="panel-title" style="color: black !important">
|
||||||
|
@if (count($expenses) && count($averageInvoice))
|
||||||
|
<div class="pull-right" style="font-size:14px;padding-top:4px;font-weight:bold">
|
||||||
|
@foreach ($averageInvoice as $item)
|
||||||
|
<span class="currency currency_{{ $item->currency_id ?: $account->getCurrencyId() }}" style="display:none">
|
||||||
|
{{ trans('texts.average_invoice') }}
|
||||||
|
{{ Utils::formatMoney($item->invoice_avg, $item->currency_id) }} |
|
||||||
|
</span>
|
||||||
|
@endforeach
|
||||||
|
<span class="average-div" style="color:#337ab7"/>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<i class="glyphicon glyphicon-ok-sign"></i> {{ trans('texts.recent_payments') }}
|
<i class="glyphicon glyphicon-ok-sign"></i> {{ trans('texts.recent_payments') }}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -172,7 +404,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default dashboard" style="height:320px">
|
<div class="panel panel-default dashboard" style="height:320px">
|
||||||
<div class="panel-heading" style="background-color:#e37329 !important">
|
<div class="panel-heading" style="background-color:#777 !important">
|
||||||
<h3 class="panel-title in-bold-white">
|
<h3 class="panel-title in-bold-white">
|
||||||
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }}
|
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.invoices_past_due') }}
|
||||||
</h3>
|
</h3>
|
||||||
@ -242,7 +474,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="panel panel-default dashboard" style="height:320px">
|
<div class="panel panel-default dashboard" style="height:320px">
|
||||||
<div class="panel-heading" style="background-color:#e37329 !important">
|
<div class="panel-heading" style="background-color:#777 !important">
|
||||||
<h3 class="panel-title in-bold-white">
|
<h3 class="panel-title in-bold-white">
|
||||||
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.expired_quotes') }}
|
<i class="glyphicon glyphicon-time"></i> {{ trans('texts.expired_quotes') }}
|
||||||
</h3>
|
</h3>
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
<link href="{{ asset('css/built.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
|
<link href="{{ asset('css/built.css') }}?no_cache={{ NINJA_VERSION }}" rel="stylesheet" type="text/css"/>
|
||||||
|
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
|
|
||||||
@if (Auth::check() && Auth::user()->dark_mode)
|
@if (Auth::check() && Auth::user()->dark_mode)
|
||||||
body {
|
body {
|
||||||
background: #000 !important;
|
background: #000 !important;
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
for (var i=0; i<currencies.length; i++) {
|
for (var i=0; i<currencies.length; i++) {
|
||||||
var currency = currencies[i];
|
var currency = currencies[i];
|
||||||
currencyMap[currency.id] = currency;
|
currencyMap[currency.id] = currency;
|
||||||
|
currencyMap[currency.code] = currency;
|
||||||
}
|
}
|
||||||
|
|
||||||
var countries = {!! \Cache::get('countries') !!};
|
var countries = {!! \Cache::get('countries') !!};
|
||||||
@ -28,7 +29,7 @@
|
|||||||
NINJA.parseFloat = function(str) {
|
NINJA.parseFloat = function(str) {
|
||||||
if (!str) return '';
|
if (!str) return '';
|
||||||
str = (str+'').replace(/[^0-9\.\-]/g, '');
|
str = (str+'').replace(/[^0-9\.\-]/g, '');
|
||||||
|
|
||||||
return window.parseFloat(str);
|
return window.parseFloat(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,4 +98,4 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -69,10 +69,14 @@
|
|||||||
} else {
|
} else {
|
||||||
// response contains id and card, which contains additional card details
|
// response contains id and card, which contains additional card details
|
||||||
var token = response.id;
|
var token = response.id;
|
||||||
// Insert the token into the form so it gets submitted to the server
|
if (token) {
|
||||||
$form.append($('<input type="hidden" name="sourceToken"/>').val(token));
|
// Insert the token into the form so it gets submitted to the server
|
||||||
// and submit
|
$form.append($('<input type="hidden" name="sourceToken"/>').val(token));
|
||||||
$form.get(0).submit();
|
// and submit
|
||||||
|
$form.get(0).submit();
|
||||||
|
} else {
|
||||||
|
logError(JSON.stringify(response));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,14 +1,8 @@
|
|||||||
@extends('header')
|
@extends('header')
|
||||||
|
|
||||||
@section('head')
|
|
||||||
@parent
|
|
||||||
|
|
||||||
<script src="{!! asset('js/Chart.min.js') !!}" type="text/javascript"></script>
|
|
||||||
@stop
|
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
@parent
|
@parent
|
||||||
@include('accounts.nav', ['selected' => ACCOUNT_CHARTS_AND_REPORTS, 'advanced' => true])
|
@include('accounts.nav', ['selected' => ACCOUNT_REPORTS, 'advanced' => true])
|
||||||
|
|
||||||
|
|
||||||
{!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required'])->addClass('warn-on-exit') !!}
|
{!! Former::open()->rules(['start_date' => 'required', 'end_date' => 'required'])->addClass('warn-on-exit') !!}
|
||||||
@ -54,18 +48,12 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
{!! Former::checkbox('enable_report')->text(trans('texts.enable'))->check($enableReport)->forceValue(1) !!}
|
|
||||||
{!! Former::select('report_type')->options($reportTypes, $reportType)->label(trans('texts.type')) !!}
|
{!! Former::select('report_type')->options($reportTypes, $reportType)->label(trans('texts.type')) !!}
|
||||||
<div id="dateField" style="display:{{ $reportType == ENTITY_TAX_RATE ? 'block' : 'none' }}">
|
<div id="dateField" style="display:{{ $reportType == ENTITY_TAX_RATE ? 'block' : 'none' }}">
|
||||||
{!! Former::select('date_field')->label(trans('texts.filter'))
|
{!! Former::select('date_field')->label(trans('texts.filter'))
|
||||||
->addOption(trans('texts.invoice_date'), FILTER_INVOICE_DATE)
|
->addOption(trans('texts.invoice_date'), FILTER_INVOICE_DATE)
|
||||||
->addOption(trans('texts.payment_date'), FILTER_PAYMENT_DATE) !!}
|
->addOption(trans('texts.payment_date'), FILTER_PAYMENT_DATE) !!}
|
||||||
</div>
|
</div>
|
||||||
<p> </p>
|
|
||||||
{!! Former::checkbox('enable_chart')->text(trans('texts.enable'))->check($enableChart)->forceValue(1) !!}
|
|
||||||
{!! Former::select('group_by')->options($dateTypes, $groupBy) !!}
|
|
||||||
{!! Former::select('chart_type')->options($chartTypes, $chartType) !!}
|
|
||||||
|
|
||||||
|
|
||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
</div>
|
</div>
|
||||||
@ -73,7 +61,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if ($enableReport)
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<table class="table table-striped invoice-table">
|
<table class="table table-striped invoice-table">
|
||||||
@ -128,29 +115,6 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
|
|
||||||
@if ($enableChart)
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body">
|
|
||||||
<canvas id="monthly-reports" width="700" height="400"></canvas>
|
|
||||||
<p> </p>
|
|
||||||
<div style="padding-bottom:8px">
|
|
||||||
<div style="float:left; height:22px; width:60px; background-color:rgba(78,205,196,.5); border: 1px solid rgba(78,205,196,1)"></div>
|
|
||||||
<div style="vertical-align: middle"> Invoices</div>
|
|
||||||
</div>
|
|
||||||
<div style="padding-bottom:8px; clear:both">
|
|
||||||
<div style="float:left; height:22px; width:60px; background-color:rgba(255,107,107,.5); border: 1px solid rgba(255,107,107,1)"></div>
|
|
||||||
<div style="vertical-align: middle"> Payments</div>
|
|
||||||
</div>
|
|
||||||
<div style="clear:both">
|
|
||||||
<div style="float:left; height:22px; width:60px; background-color:rgba(199,244,100,.5); border: 1px solid rgba(199,244,100,1)"></div>
|
|
||||||
<div style="vertical-align: middle"> Credits</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -162,32 +126,6 @@
|
|||||||
$('#action').val('');
|
$('#action').val('');
|
||||||
}
|
}
|
||||||
|
|
||||||
@if ($enableChart)
|
|
||||||
var ctx = document.getElementById('monthly-reports').getContext('2d');
|
|
||||||
var chart = {
|
|
||||||
labels: {!! json_encode($labels) !!},
|
|
||||||
datasets: [
|
|
||||||
@foreach ($datasets as $dataset)
|
|
||||||
{
|
|
||||||
data: {!! json_encode($dataset['totals']) !!},
|
|
||||||
fillColor : "rgba({!! $dataset['colors'] !!},0.5)",
|
|
||||||
strokeColor : "rgba({!! $dataset['colors'] !!},1)",
|
|
||||||
},
|
|
||||||
@endforeach
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
scaleOverride: true,
|
|
||||||
scaleSteps: 10,
|
|
||||||
scaleStepWidth: {!! $scaleStepWidth !!},
|
|
||||||
scaleStartValue: 0,
|
|
||||||
scaleLabel : "<%=value%>",
|
|
||||||
};
|
|
||||||
|
|
||||||
new Chart(ctx).{!! $chartType !!}(chart, options);
|
|
||||||
@endif
|
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$('.start_date .input-group-addon').click(function() {
|
$('.start_date .input-group-addon').click(function() {
|
||||||
toggleDatePicker('start_date');
|
toggleDatePicker('start_date');
|
||||||
|
Loading…
Reference in New Issue
Block a user