finish basic public dashboard
This commit is contained in:
parent
d5631297dc
commit
ff94df53d6
|
@ -1,70 +1,22 @@
|
|||
<?php
|
||||
use Engelsystem\Database\Db;
|
||||
|
||||
/**
|
||||
* Loads all data for the public dashboard
|
||||
*/
|
||||
function public_dashboard_controller()
|
||||
{
|
||||
$stats = [];
|
||||
$stats = [
|
||||
'needed-3-hours' => stats_angels_needed_three_hours(),
|
||||
'needed-night' => stats_angels_needed_for_nightshifts(),
|
||||
'angels-working' => stats_currently_working(),
|
||||
'hours-to-work' => stats_hours_to_work()
|
||||
];
|
||||
|
||||
$now = time();
|
||||
$in3hours = $now + 3 * 60 * 60;
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
- (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE ((`end` > ? AND `end` < ?) OR (`start` > ? AND `start` < ?))", [
|
||||
$now,
|
||||
$in3hours,
|
||||
$now,
|
||||
$in3hours
|
||||
]);
|
||||
$stats['needed-3-hours'] = $result['count'];
|
||||
|
||||
$night_start = parse_date('Y-m-d H:i', date('Y-m-d', time() + 12 * 60 * 60) . ' 02:00');
|
||||
$night_end = $night_start + 6 * 60 * 60;
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
- (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE ((`end` > ? AND `end` < ?) OR (`start` > ? AND `start` < ?))", [
|
||||
$night_start,
|
||||
$night_end,
|
||||
$night_start,
|
||||
$night_end
|
||||
]);
|
||||
$stats['needed-night'] = $result['count'];
|
||||
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE (`end` >= ? AND `start` <= ?)", [
|
||||
time(),
|
||||
time()
|
||||
]);
|
||||
$stats['angels-working'] = $result['count'];
|
||||
|
||||
$result = Db::selectOne("
|
||||
SELECT ROUND(SUM(
|
||||
(SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
* (`Shifts`.`end` - `Shifts`.`start`)/3600
|
||||
)) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE `end` >= ?", [
|
||||
time()
|
||||
]);
|
||||
$stats['hours-to-work'] = $result['count'];
|
||||
$free_shifts = Shifts_free(time(), time() + 12 * 60 * 60);
|
||||
|
||||
return [
|
||||
'Engelsystem Public Dashboard',
|
||||
public_dashboard_view($stats)
|
||||
_('Engelsystem Public Dashboard'),
|
||||
public_dashboard_view($stats, $free_shifts)
|
||||
];
|
||||
}
|
||||
?>
|
|
@ -22,6 +22,7 @@ $includeFiles = [
|
|||
__DIR__ . '/../includes/model/ShiftsFilter.php',
|
||||
__DIR__ . '/../includes/model/ShiftSignupState.php',
|
||||
__DIR__ . '/../includes/model/ShiftTypes_model.php',
|
||||
__DIR__ . '/../includes/model/Stats.php',
|
||||
__DIR__ . '/../includes/model/UserAngelTypes_model.php',
|
||||
__DIR__ . '/../includes/model/UserDriverLicenses_model.php',
|
||||
__DIR__ . '/../includes/model/UserGroups_model.php',
|
||||
|
|
|
@ -26,6 +26,29 @@ function Shifts_by_angeltype($angeltype) {
|
|||
', [$angeltype['id'], $angeltype['id']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns every shift with needed angels in the given time range.
|
||||
*/
|
||||
function Shifts_free($start, $end)
|
||||
{
|
||||
$shifts = Db::select("
|
||||
SELECT *
|
||||
FROM `Shifts`
|
||||
WHERE (`end` > ? AND `start` < ?)
|
||||
AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
> (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
ORDER BY `start`
|
||||
", [
|
||||
$start,
|
||||
$end
|
||||
]);
|
||||
$free_shifts = [];
|
||||
foreach ($shifts as $shift) {
|
||||
$free_shifts[] = Shift($shift['SID']);
|
||||
}
|
||||
return $free_shifts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all shifts with a PSID (from frab import)
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
use Engelsystem\Database\Db;
|
||||
|
||||
/**
|
||||
* Returns the number of angels currently working.
|
||||
*/
|
||||
function stats_currently_working()
|
||||
{
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE (`end` >= ? AND `start` <= ?)", [
|
||||
time(),
|
||||
time()
|
||||
]);
|
||||
if (empty($result['count'])) {
|
||||
return '-';
|
||||
}
|
||||
return $result['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of hours still to work.
|
||||
*/
|
||||
function stats_hours_to_work()
|
||||
{
|
||||
$result = Db::selectOne("
|
||||
SELECT ROUND(SUM(
|
||||
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
* (`Shifts`.`end` - `Shifts`.`start`)/3600
|
||||
)) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE `end` >= ?", [
|
||||
time()
|
||||
]);
|
||||
if (empty($result['count'])) {
|
||||
return '-';
|
||||
}
|
||||
return $result['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of needed angels in the next 3 hours
|
||||
*/
|
||||
function stats_angels_needed_three_hours()
|
||||
{
|
||||
$now = time();
|
||||
$in3hours = $now + 3 * 60 * 60;
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
- (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE ((`end` > ? AND `end` < ?) OR (`start` > ? AND `start` < ?))", [
|
||||
$now,
|
||||
$in3hours,
|
||||
$now,
|
||||
$in3hours
|
||||
]);
|
||||
if (empty($result['count'])) {
|
||||
return '-';
|
||||
}
|
||||
return $result['count'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of needed angels for nightshifts (between 2 and 8)
|
||||
*/
|
||||
function stats_angels_needed_for_nightshifts()
|
||||
{
|
||||
$night_start = parse_date('Y-m-d H:i', date('Y-m-d', time() + 12 * 60 * 60) . ' 02:00');
|
||||
$night_end = $night_start + 6 * 60 * 60;
|
||||
$result = Db::selectOne("
|
||||
SELECT SUM(
|
||||
(SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`Shifts`.`SID`)
|
||||
- (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`Shifts`.`SID` AND `freeloaded`=0)
|
||||
) as `count`
|
||||
FROM `Shifts`
|
||||
WHERE ((`end` > ? AND `end` < ?) OR (`start` > ? AND `start` < ?))", [
|
||||
$night_start,
|
||||
$night_end,
|
||||
$night_start,
|
||||
$night_end
|
||||
]);
|
||||
if (empty($result['count'])) {
|
||||
return '-';
|
||||
}
|
||||
return $result['count'];
|
||||
}
|
||||
|
||||
?>
|
|
@ -1,5 +1,30 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Render a stat for dashborad (big number with label).
|
||||
* If no style given, style is danger if number > 0, and success if number == 0.
|
||||
*
|
||||
* @param string $label
|
||||
* @param string $number
|
||||
* @param string $style default, warning, danger or success. Optional.
|
||||
*/
|
||||
function stats($label, $number, $style = null)
|
||||
{
|
||||
if(empty($style)) {
|
||||
if($number > 0) {
|
||||
$style = 'danger';
|
||||
} else {
|
||||
$style = 'success';
|
||||
}
|
||||
}
|
||||
return div('stats stats-' . $style, [
|
||||
$label,
|
||||
div('number', [
|
||||
$number
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders tabs from the array. Array key is tab name, array value is tab content.
|
||||
*
|
||||
|
|
|
@ -3,29 +3,62 @@
|
|||
/**
|
||||
* Public dashboard (formerly known as angel news hub)
|
||||
*/
|
||||
function public_dashboard_view($stats)
|
||||
function public_dashboard_view($stats, $free_shifts)
|
||||
{
|
||||
$shift_panels = [];
|
||||
foreach ($free_shifts as $shift) {
|
||||
$shift_panels[] = public_dashborad_shift_render($shift);
|
||||
}
|
||||
return page([
|
||||
div('first', [
|
||||
div('col-xs-3 text-center', [
|
||||
_('Angels needed in the next 3 hrs'),
|
||||
heading($stats['needed-3-hours'], 1)
|
||||
]),
|
||||
div('col-xs-3 text-center', [
|
||||
_('Angels needed for nightshifts'),
|
||||
heading($stats['needed-night'], 1)
|
||||
]),
|
||||
div('col-xs-3 text-center', [
|
||||
_('Angels currently working'),
|
||||
heading($stats['angels-working'], 1)
|
||||
]),
|
||||
div('col-xs-3 text-center', [
|
||||
_('Hours to be worked'),
|
||||
heading($stats['hours-to-work'], 1)
|
||||
]),
|
||||
div('first container-fluid', [
|
||||
stats(_('Angels needed in the next 3 hrs'), $stats['needed-3-hours']),
|
||||
stats(_('Angels needed for nightshifts'), $stats['needed-night']),
|
||||
stats(_('Angels currently working'), $stats['angels-working'], 'default'),
|
||||
stats(_('Hours to be worked'), $stats['hours-to-work'], 'default'),
|
||||
'<script>$(function(){setTimeout(function(){window.location.reload();}, 60000)})</script>'
|
||||
]),
|
||||
div('container-fluid first', [
|
||||
heading(_('Needed angels:'), 1),
|
||||
join($shift_panels)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single shift panel for a dashboard shift with needed angels
|
||||
*/
|
||||
function public_dashborad_shift_render($shift)
|
||||
{
|
||||
$style = 'default';
|
||||
if (time() + 3 * 60 * 60 > $shift['start']) {
|
||||
$style = 'warning';
|
||||
}
|
||||
if (time() > $shift['start']) {
|
||||
$style = 'danger';
|
||||
}
|
||||
|
||||
$panel_body = glyph('time') . date('H:i', $shift['start']) . ' - ' . date('H:i', $shift['end']);
|
||||
|
||||
$panel_body .= '<br>' . glyph('tasks') . ShiftType($shift['shifttype_id'])['name'];
|
||||
if (! empty($shift['title'])) {
|
||||
$panel_body .= ' (' . $shift['title'] . ')';
|
||||
}
|
||||
|
||||
$panel_body .= '<br>' . glyph('map-marker') . Room($shift['RID'])['Name'];
|
||||
|
||||
foreach ($shift['NeedAngels'] as $needed_angels) {
|
||||
$need = $needed_angels['count'] - $needed_angels['taken'];
|
||||
if ($need > 0) {
|
||||
$panel_body .= '<br>' . glyph('user') . $need . ' × ' . AngelType($needed_angels['TID'])['name'];
|
||||
}
|
||||
}
|
||||
|
||||
$panel_body = '<a href="' . shift_link($shift) . '">' . $panel_body . '</a>';
|
||||
|
||||
return div('panel panel-' . $style . ' col-xs-3', [
|
||||
div('panel-body', [
|
||||
heading($panel_body, 4)
|
||||
])
|
||||
]);
|
||||
}
|
||||
?>
|
|
@ -6736,6 +6736,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #d9534f;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
.stats-success {
|
||||
color: #5cb85c;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6759,6 +6759,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #d9534f;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #f0ad4e;
|
||||
}
|
||||
.stats-success {
|
||||
color: #5cb85c;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6736,6 +6736,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #7f528b;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #e3a14d;
|
||||
}
|
||||
.stats-success {
|
||||
color: #7b9c41;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6745,6 +6745,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #da1639;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #dad216;
|
||||
}
|
||||
.stats-success {
|
||||
color: #39ab50;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6759,6 +6759,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #ff263c;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #fff45f;
|
||||
}
|
||||
.stats-success {
|
||||
color: #8dc123;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6739,6 +6739,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #ff6600;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #ffff33;
|
||||
}
|
||||
.stats-success {
|
||||
color: #99cc00;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -6762,6 +6762,28 @@ body {
|
|||
line-height: 30px;
|
||||
margin: 0px;
|
||||
}
|
||||
.stats {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 25%;
|
||||
min-height: 1px;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
.stats .number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
.stats-danger {
|
||||
color: #ff6600;
|
||||
}
|
||||
.stats-warning {
|
||||
color: #ffff33;
|
||||
}
|
||||
.stats-success {
|
||||
color: #99cc00;
|
||||
}
|
||||
.panel-primary .panel-heading a {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,28 @@ body {
|
|||
margin: 0px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
.make-xs-column(3);
|
||||
text-align: center;
|
||||
|
||||
.number {
|
||||
font-size: 80px;
|
||||
font-weight: 200;
|
||||
}
|
||||
}
|
||||
|
||||
.stats-danger {
|
||||
color: @brand-danger;
|
||||
}
|
||||
|
||||
.stats-warning {
|
||||
color: @brand-warning;
|
||||
}
|
||||
|
||||
.stats-success {
|
||||
color: @brand-success;
|
||||
}
|
||||
|
||||
.panel-primary .panel-heading a {
|
||||
color: @panel-primary-text;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue