From ff94df53d69234ae2625462a76926e131ade05b7 Mon Sep 17 00:00:00 2001 From: msquare Date: Tue, 12 Dec 2017 21:57:57 +0100 Subject: [PATCH] finish basic public dashboard --- .../public_dashboard_controller.php | 66 ++----------- includes/includes.php | 1 + includes/model/Shifts_model.php | 23 +++++ includes/model/Stats.php | 94 +++++++++++++++++++ includes/sys_template.php | 25 +++++ includes/view/PublicDashboard_view.php | 69 ++++++++++---- public/css/theme0.css | 22 +++++ public/css/theme1.css | 22 +++++ public/css/theme2.css | 22 +++++ public/css/theme3.css | 22 +++++ public/css/theme4.css | 22 +++++ public/css/theme5.css | 22 +++++ public/css/theme6.css | 22 +++++ themes/base.less | 22 +++++ 14 files changed, 379 insertions(+), 75 deletions(-) create mode 100644 includes/model/Stats.php diff --git a/includes/controller/public_dashboard_controller.php b/includes/controller/public_dashboard_controller.php index dc81ba2c..bf909bed 100644 --- a/includes/controller/public_dashboard_controller.php +++ b/includes/controller/public_dashboard_controller.php @@ -1,70 +1,22 @@ 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) ]; } ?> \ No newline at end of file diff --git a/includes/includes.php b/includes/includes.php index c533cfe8..b6bd41cf 100644 --- a/includes/includes.php +++ b/includes/includes.php @@ -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', diff --git a/includes/model/Shifts_model.php b/includes/model/Shifts_model.php index 1fc7fe89..b0d82a5b 100644 --- a/includes/model/Shifts_model.php +++ b/includes/model/Shifts_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) */ diff --git a/includes/model/Stats.php b/includes/model/Stats.php new file mode 100644 index 00000000..5618cdc6 --- /dev/null +++ b/includes/model/Stats.php @@ -0,0 +1,94 @@ += ? 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']; +} + +?> \ No newline at end of file diff --git a/includes/sys_template.php b/includes/sys_template.php index 819b7d1c..b5846e9b 100644 --- a/includes/sys_template.php +++ b/includes/sys_template.php @@ -1,5 +1,30 @@ 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. * diff --git a/includes/view/PublicDashboard_view.php b/includes/view/PublicDashboard_view.php index 6fa40ed4..7b15c7dd 100644 --- a/includes/view/PublicDashboard_view.php +++ b/includes/view/PublicDashboard_view.php @@ -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'), '' + ]), + 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 .= '
' . glyph('tasks') . ShiftType($shift['shifttype_id'])['name']; + if (! empty($shift['title'])) { + $panel_body .= ' (' . $shift['title'] . ')'; + } + + $panel_body .= '
' . 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 .= '
' . glyph('user') . $need . ' × ' . AngelType($needed_angels['TID'])['name']; + } + } + + $panel_body = '' . $panel_body . ''; + + return div('panel panel-' . $style . ' col-xs-3', [ + div('panel-body', [ + heading($panel_body, 4) + ]) + ]); +} ?> \ No newline at end of file diff --git a/public/css/theme0.css b/public/css/theme0.css index 050f689e..35a35f9e 100644 --- a/public/css/theme0.css +++ b/public/css/theme0.css @@ -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; } diff --git a/public/css/theme1.css b/public/css/theme1.css index 06ce9907..06979c3e 100644 --- a/public/css/theme1.css +++ b/public/css/theme1.css @@ -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; } diff --git a/public/css/theme2.css b/public/css/theme2.css index 9ca9babb..ad7770a4 100644 --- a/public/css/theme2.css +++ b/public/css/theme2.css @@ -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; } diff --git a/public/css/theme3.css b/public/css/theme3.css index abb90f23..f5dcce0e 100644 --- a/public/css/theme3.css +++ b/public/css/theme3.css @@ -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; } diff --git a/public/css/theme4.css b/public/css/theme4.css index 24771afd..554cf166 100644 --- a/public/css/theme4.css +++ b/public/css/theme4.css @@ -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; } diff --git a/public/css/theme5.css b/public/css/theme5.css index 98922778..f3c345a2 100644 --- a/public/css/theme5.css +++ b/public/css/theme5.css @@ -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; } diff --git a/public/css/theme6.css b/public/css/theme6.css index c30379fb..e8601992 100644 --- a/public/css/theme6.css +++ b/public/css/theme6.css @@ -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; } diff --git a/themes/base.less b/themes/base.less index 677f3200..35473860 100644 --- a/themes/base.less +++ b/themes/base.less @@ -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; }