getting ready for customizeable dashboard with widget

This commit is contained in:
iBNu Maksum
2025-02-14 17:08:05 +07:00
parent 30bdb89d91
commit eff0c7dab7
27 changed files with 1112 additions and 2 deletions

View File

@ -0,0 +1,66 @@
{include file="sections/header.tpl"}
{function showWidget pos=0}
{foreach $widgets as $w}
{if $w['position'] == $pos}
{$w['content']}
{/if}
{/foreach}
{/function}
{showWidget widgets=$widgets pos=1}
<div class="row">
<div class="col-md-7">
{showWidget widgets=$widgets pos=2}
</div>
<div class="col-md-5">
{showWidget widgets=$widgets pos=3}
</div>
</div>
{showWidget widgets=$widgets pos=4}
{if $_c['new_version_notify'] != 'disable'}
<script>
window.addEventListener('DOMContentLoaded', function() {
$.getJSON("./version.json?" + Math.random(), function(data) {
var localVersion = data.version;
$('#version').html('Version: ' + localVersion);
$.getJSON(
"https://raw.githubusercontent.com/hotspotbilling/phpnuxbill/master/version.json?" +
Math
.random(),
function(data) {
var latestVersion = data.version;
if (localVersion !== latestVersion) {
$('#version').html('Latest Version: ' + latestVersion);
if (getCookie(latestVersion) != 'done') {
Swal.fire({
icon: 'info',
title: "New Version Available\nVersion: " + latestVersion,
toast: true,
position: 'bottom-right',
showConfirmButton: true,
showCloseButton: true,
timer: 30000,
confirmButtonText: '<a href="{Text::url('community')}#latestVersion" style="color: white;">Update Now</a>',
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal
.resumeTimer)
}
});
setCookie(latestVersion, 'done', 7);
}
}
});
});
});
</script>
{/if}
{include file="sections/footer.tpl"}

View File

@ -20,8 +20,10 @@
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/sweetalert2.min.css" />
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/plugins/pace.css" />
<link rel="stylesheet" href="{$app_url}/ui/ui/summernote/summernote.min.css" />
<script src="{$app_url}/ui/ui/scripts/sweetalert2.all.min.js"></script>
<link rel="stylesheet" href="{$app_url}/ui/ui/styles/phpnuxbill.css?2025.2.4" />
<script src="{$app_url}/ui/ui/scripts/sweetalert2.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
<style>
</style>

View File

@ -0,0 +1,73 @@
{include file="sections/header.tpl"}
{function showWidget pos=0}
<form method="post" action="{Text::url('widgets/pos/')}">
<div class="panel panel-info">
<div class="panel-heading">Area {$pos}</div>
<div class="panel-body">
{foreach $widgets as $w}
{if $w['position'] == $pos}
<div class="panel panel-{if $w['enabled']}default{else}danger{/if}">
<div class="panel-heading"><b>{$w['title']}</b></div>
<div class="panel-body">{ucwords(str_replace('.php', '', str_replace('_', ' ', $w['widget'])))}</div>
<table class="table table-bordered table-condensed">
<tr>
<td>
<div class="input-group">
<span class="input-group-addon">orders</span>
<input type="number" style="width: 100px;" name="orders[]" value="{$w['id']}"
class="form-control" placeholder="orders">
</div>
<input type="hidden" name="id" value="{$w['id']}">
</td>
<td width="130">
<div class="btn-group btn-group-justified" role="group">
<a href="{Text::url('widgets/edit/', $w['id'])}" class="btn btn-sm btn-success">edit</a>
<a href="{Text::url('widgets/delete/', $w['id'])}"
onclick="return ask(this, 'Delete this widget?')" class="btn btn-sm btn-danger"><i
class="glyphicon glyphicon-trash"></i></a>
</div>
</td>
</tr>
</table>
</div>
{/if}
{/foreach}
</div>
<div class="panel-footer">
<div class="btn-group btn-group-justified" role="group">
<div class="btn-group" role="group">
<button type="submit" class="btn btn-info">Save Orders</button>
</div>
<a href="{Text::url('widgets/add/', $pos)}" class="btn btn-primary">Add new widget</a>
</div>
</div>
</div>
</form>
{/function}
<div class="row">
<div class="col-md-6 col-md-offset-3">
{showWidget widgets=$widgets pos=1}
</div>
</div>
<div class="row">
<div class="col-md-7 border">
{showWidget widgets=$widgets pos=2}
</div>
<div class="col-md-5 border">
{showWidget widgets=$widgets pos=3}
</div>
</div>
<div class="row">
<div class="col-md-6 col-md-offset-3">
{showWidget widgets=$widgets pos=4}
</div>
</div>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,92 @@
{include file="sections/header.tpl"}
<form method="post" action="{Text::url('widgets/', $do)}">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<div class="panel panel-info">
<div class="panel-heading">{if $do == 'add'}{Lang::T('Add')}{else}{Lang::T('Edit')}{/if} Widget</div>
<div class="panel-body">
<input type="hidden" class="form-control" required id="id" name="id" value="{$widget['id']}">
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Title')}</label>
<div class="col-md-5">
<input type="text" class="form-control" required id="title" name="title"
value="{$widget['title']}">
</div>
<span class="help-block col-md-4">&nbsp;</span>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Order')}</label>
<div class="col-md-5">
<input type="number" class="form-control" id="orders" name="orders"
value="{if empty($widget['orders'])}99{else}{$widget['orders']}{/if}">
</div>
<span class="help-block col-md-4">&nbsp;</span>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Position')}</label>
<div class="col-md-5">
<select name="position" id="position" class="form-control">
{for $pos=1 to 4}
<option value="{$pos}" {if $widget['position'] eq $pos}selected="selected" {/if}>
Area {$pos}</option>
{/for}
</select>
</div>
<p class="help-block col-md-4">&nbsp;</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Widget')}</label>
<div class="col-md-5">
<select name="widget" id="widget" class="form-control">
{foreach $widgets as $k => $v}
<option value="{$k}" {if $widget['widget'] eq $k}selected="selected" {/if}>
{$v}
</option>
{/foreach}
</select>
</div>
<p class="help-block col-md-4">&nbsp;</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Enabled')}</label>
<div class="col-md-5">
<select name="enabled" id="enabled" class="form-control">
<option value="1" {if $widget['enabled'] neq 1}selected="selected" {/if}>
{Lang::T('Active')}</option>
<option value="0" {if $widget['enabled'] eq 0}selected="selected" {/if}>
{Lang::T('Inactive')}</option>
<option value="1" {if $widget['enabled'] neq 1}selected="selected" {/if}>
{Lang::T('Active')}</option>
</select>
</div>
<p class="help-block col-md-4">&nbsp;</p>
</div>
<div class="form-group">
<label class="col-md-3 control-label">{Lang::T('Contents')}</label>
<div class="col-md-9">
<textarea name="content" rows="5" id="content"
class="form-control">{$widget['content']}</textarea>
<p class="help-block">Not All Widget need contents, HTML widget need content, it can be text
or PHP Code, be careful</p>
</div>
</div>
</div>
<div class="panel-footer">
<div class="btn-group btn-group-justified" role="group">
<a href="{Text::url('widgets/')}" class="btn btn-default btn-block">{Lang::T('Back')}</a>
<div class="btn-group" role="group">
<button type="submit" class="btn btn-info btn-block">{Lang::T('Save')}</button>
</div>
</div>
</div>
</div>
{if $do == 'edit'}
<a href="{Text::url('widgets/delete/', $widget['id'])}" class="btn btn-danger btn-xs"
onclick="return ask(this, 'Delete this widget?')">{Lang::T('Delete')}</a>
{/if}
</div>
</div>
</form>
{include file="sections/footer.tpl"}

View File

@ -0,0 +1,14 @@
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading"><a href="{Text::url('logs')}">{Lang::T('Activity Log')}</a></div>
<div class="panel-body">
<ul class="list-unstyled">
{foreach $dlog as $dlogs}
<li class="primary">
<span class="point"></span>
<span class="time small text-muted">{Lang::timeElapsed($dlogs['date'],true)}</span>
<p>{$dlogs['description']}</p>
</li>
{/foreach}
</ul>
</div>
</div>

View File

@ -0,0 +1,5 @@
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading">{$card_header}</div>
<div class="panel-body">{$card_body}</ul>
</div>
</div>

View File

@ -0,0 +1,19 @@
{if $run_date}
{assign var="current_time" value=$smarty.now}
{assign var="run_time" value=strtotime($run_date)}
{if $current_time - $run_time > 3600}
<div class="panel panel-cron-warning panel-hovered mb20 activities">
<div class="panel-heading"><i class="fa fa-clock-o"></i> &nbsp; {Lang::T('Cron has not run for over 1 hour. Please
check your setup.')}</div>
</div>
{else}
<div class="panel panel-cron-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Cron Job last ran on')}: {$run_date}</div>
</div>
{/if}
{else}
<div class="panel panel-cron-danger panel-hovered mb20 activities">
<div class="panel-heading"><i class="fa fa-warning"></i> &nbsp; {Lang::T('Cron appear not been setup, please check
your cron setup.')}</div>
</div>
{/if}

View File

@ -0,0 +1,33 @@
<div class="panel panel-warning mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">{Lang::T('Customers Expired, Today')}</div>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<th>{Lang::T('Username')}</th>
<th>{Lang::T('Created / Expired')}</th>
<th>{Lang::T('Internet Package')}</th>
<th>{Lang::T('Location')}</th>
</tr>
</thead>
<tbody>
{foreach $expire as $expired}
{assign var="rem_exp" value="{$expired['expiration']} {$expired['time']}"}
{assign var="rem_started" value="{$expired['recharged_on']} {$expired['recharged_time']}"}
<tr>
<td><a href="{Text::url('customers/viewu/',$expired['username'])}">{$expired['username']}</a></td>
<td><small data-toggle="tooltip" data-placement="top"
title="{Lang::dateAndTimeFormat($expired['recharged_on'],$expired['recharged_time'])}">{Lang::timeElapsed($rem_started)}</small>
/
<span data-toggle="tooltip" data-placement="top"
title="{Lang::dateAndTimeFormat($expired['expiration'],$expired['time'])}">{Lang::timeElapsed($rem_exp)}</span>
</td>
<td>{$expired['namebp']}</td>
<td>{$expired['routers']}</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
&nbsp; {include file="pagination.tpl"}
</div>

View File

@ -0,0 +1,10 @@
<ol class="breadcrumb">
<li>{Lang::dateFormat($start_date)}</li>
<li>{Lang::dateFormat($current_date)}</li>
{if $_c['enable_balance'] == 'yes' && in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])}
<li onclick="window.location.href = '{Text::url('customers&search=&order=balance&filter=Active&orderby=desc')}'" style="cursor: pointer;">
{Lang::T('Customer Balance')} <sup>{$_c['currency_code']}</sup>
<b>{number_format($cb,0,$_c['dec_point'],$_c['thousands_sep'])}</b>
</li>
{/if}
</ol>

View File

@ -0,0 +1,59 @@
<div class="panel panel-info panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('All Users Insights')}</div>
<div class="panel-body">
<canvas id="userRechargesChart"></canvas>
</div>
</div>
<script type="text/javascript">
{if $_c['hide_aui'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
// Get the data from PHP and assign it to JavaScript variables
var u_act = '{/literal}{$u_act}{literal}';
var c_all = '{/literal}{$c_all}{literal}';
var u_all = '{/literal}{$u_all}{literal}';
//lets calculate the inactive users as reported
var expired = u_all - u_act;
var inactive = c_all - u_all;
if (inactive < 0) {
inactive = 0;
}
// Create the chart data
var data = {
labels: ['Active Users', 'Expired Users', 'Inactive Users'],
datasets: [{
label: 'User Recharges',
data: [parseInt(u_act), parseInt(expired), parseInt(inactive)],
backgroundColor: ['rgba(4, 191, 13)', 'rgba(191, 35, 4)', 'rgba(0, 0, 255, 0.5'],
borderColor: ['rgba(0, 255, 0, 1)', 'rgba(255, 99, 132, 1)', 'rgba(0, 0, 255, 0.7'],
borderWidth: 1
}]
};
// Create chart options
var options = {
responsive: true,
aspectRatio: 1,
plugins: {
legend: {
position: 'bottom',
labels: {
boxWidth: 15
}
}
}
};
// Get the canvas element and create the chart
var ctx = document.getElementById('userRechargesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'pie',
data: data,
options: options
});
});
{/literal}
{/if}
</script>

View File

@ -0,0 +1,73 @@
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-th"></i>
<h3 class="box-title">{Lang::T('Monthly Registered Customers')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<a href="{Text::url('dashboard&refresh')}" class="btn bg-teal btn-sm"><i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="chart" style="height: 250px;"></canvas>
</div>
</div>
<script type="text/javascript">
{if $_c['hide_mrc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var counts = JSON.parse('{/literal}{$monthlyRegistered|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = counts.find(count => count.date === i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.count : 0);
}
var ctx = document.getElementById('chart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Registered Members',
data: data,
backgroundColor: 'rgba(0, 0, 255, 0.5)',
borderColor: 'rgba(0, 0, 255, 0.7)',
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
{/literal}
{/if}
</script>

View File

@ -0,0 +1,81 @@
<div class="box box-solid ">
<div class="box-header">
<i class="fa fa-inbox"></i>
<h3 class="box-title">{Lang::T('Total Monthly Sales')}</h3>
<div class="box-tools pull-right">
<button type="button" class="btn bg-teal btn-sm" data-widget="collapse"><i class="fa fa-minus"></i>
</button>
<a href="{Text::url('dashboard&refresh')}" class="btn bg-teal btn-sm"><i class="fa fa-refresh"></i>
</a>
</div>
</div>
<div class="box-body border-radius-none">
<canvas class="chart" id="salesChart" style="height: 250px;"></canvas>
</div>
</div>
<script type="text/javascript">
{if $_c['hide_tmc'] != 'yes'}
{literal}
document.addEventListener("DOMContentLoaded", function() {
var monthlySales = JSON.parse('{/literal}{$monthlySales|json_encode}{literal}');
var monthNames = [
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
];
var labels = [];
var data = [];
for (var i = 1; i <= 12; i++) {
var month = findMonthData(monthlySales, i);
labels.push(month ? monthNames[i - 1] : monthNames[i - 1].substring(0, 3));
data.push(month ? month.totalSales : 0);
}
var ctx = document.getElementById('salesChart').getContext('2d');
var chart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Monthly Sales',
data: data,
backgroundColor: 'rgba(2, 10, 242)', // Customize the background color
borderColor: 'rgba(255, 99, 132, 1)', // Customize the border color
borderWidth: 1
}]
},
options: {
responsive: true,
scales: {
x: {
grid: {
display: false
}
},
y: {
beginAtZero: true,
grid: {
color: 'rgba(0, 0, 0, 0.1)'
}
}
}
}
});
});
function findMonthData(monthlySales, month) {
for (var i = 0; i < monthlySales.length; i++) {
if (monthlySales[i].month === month) {
return monthlySales[i];
}
}
return null;
}
{/literal}
{/if}
</script>

View File

@ -0,0 +1,4 @@
<div class="panel panel-success panel-hovered mb20 activities">
<div class="panel-heading">{Lang::T('Payment Gateway')}: {str_replace(',',', ',$_c['payment_gateway'])}
</div>
</div>

View File

@ -0,0 +1,19 @@
{if $_c['router_check'] && count($routeroffs)> 0}
<div class="panel panel-danger">
<div class="panel-heading text-bold">{Lang::T('Routers Offline')}</div>
<div class="table-responsive">
<table class="table table-condensed">
<tbody>
{foreach $routeroffs as $ros}
<tr>
<td><a href="{Text::url('routers/edit/',$ros['id'])}" class="text-bold text-red">{$ros['name']}</a></td>
<td data-toggle="tooltip" data-placement="top" class="text-red"
title="{Lang::dateTimeFormat($ros['last_seen'])}">{Lang::timeElapsed($ros['last_seen'])}
</td>
</tr>
{/foreach}
</tbody>
</table>
</div>
</div>
{/if}

View File

@ -0,0 +1,50 @@
<div class="row">
{if in_array($_admin['user_type'],['SuperAdmin','Admin', 'Report'])}
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-aqua">
<div class="inner">
<h4 class="text-bold" style="font-size: large;"><sup>{$_c['currency_code']}</sup>
{number_format($iday,0,$_c['dec_point'],$_c['thousands_sep'])}</h4>
</div>
<div class="icon">
<i class="ion ion-clock"></i>
</div>
<a href="{Text::url('reports/by-date')}" class="small-box-footer">{Lang::T('Income Today')}</a>
</div>
</div>
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-green">
<div class="inner">
<h4 class="text-bold" style="font-size: large;"><sup>{$_c['currency_code']}</sup>
{number_format($imonth,0,$_c['dec_point'],$_c['thousands_sep'])}</h4>
</div>
<div class="icon">
<i class="ion ion-android-calendar"></i>
</div>
<a href="{Text::url('reports/by-period')}" class="small-box-footer">{Lang::T('Income This Month')}</a>
</div>
</div>
{/if}
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-yellow">
<div class="inner">
<h4 class="text-bold" style="font-size: large;">{$u_act}/{$u_all-$u_act}</h4>
</div>
<div class="icon">
<i class="ion ion-person"></i>
</div>
<a href="{Text::url('plan/list')}" class="small-box-footer">{Lang::T('Active')}/{Lang::T('Expired')}</a>
</div>
</div>
<div class="col-lg-3 col-xs-6">
<div class="small-box bg-red">
<div class="inner">
<h4 class="text-bold" style="font-size: large;">{$c_all}</h4>
</div>
<div class="icon">
<i class="ion ion-android-people"></i>
</div>
<a href="{Text::url('customers/list')}" class="small-box-footer">{Lang::T('Customers')}</a>
</div>
</div>
</div>

View File

@ -0,0 +1,30 @@
{if $_c['disable_voucher'] != 'yes' && $stocks['unused']>0 || $stocks['used']>0}
<div class="panel panel-primary mb20 panel-hovered project-stats table-responsive">
<div class="panel-heading">Vouchers Stock</div>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<th>{Lang::T('Package Name')}</th>
<th>unused</th>
<th>used</th>
</tr>
</thead>
<tbody>
{foreach $plans as $stok}
<tr>
<td>{$stok['name_plan']}</td>
<td>{$stok['unused']}</td>
<td>{$stok['used']}</td>
</tr>
</tbody>
{/foreach}
<tr>
<td>Total</td>
<td>{$stocks['unused']}</td>
<td>{$stocks['used']}</td>
</tr>
</table>
</div>
</div>
{/if}