improved play and stop button (#2692)

* improved play and stop button if only one record is allowed
* stop button first in action menu
* fix skin colors
* autofocus term field in search form
* fix responsive column layout in invoice listing
* improve responsive header for small screens
This commit is contained in:
Kevin Papst
2021-07-30 19:57:57 +02:00
committed by GitHub
parent 37e02365c7
commit df62c8a428
19 changed files with 353 additions and 245 deletions

View File

@@ -24,18 +24,14 @@ export default class KimaiActiveRecords extends KimaiPlugin {
}
init() {
const menu = document.querySelector(this.selector);
this.menu = document.querySelector(this.selector);
// the menu can be hidden if user has no permissions to see it
if (menu === null) {
if (this.menu === null) {
return;
}
const dropdown = menu.querySelector('ul.dropdown-menu');
this.attributes = dropdown.dataset;
this.itemList = dropdown.querySelector('li > ul.menu');
this.label = menu.querySelector('a > span.label');
this.attributes = this.menu.dataset;
const self = this;
const handle = function() { self.reloadActiveRecords(); };
@@ -50,15 +46,10 @@ export default class KimaiActiveRecords extends KimaiPlugin {
document.addEventListener('kimai.customerDelete', handle);
}
emptyList() {
this.itemList.innerHTML = '';
}
_toggleMenu(hasEntries) {
const menu = document.querySelector(this.selector);
const menuEmpty = document.querySelector(this.selectorEmpty);
this.menu.style.display = hasEntries ? 'inline-block' : 'none';
menu.style.display = hasEntries ? 'inline-block' : 'none';
const menuEmpty = document.querySelector(this.selectorEmpty);
if (menuEmpty !== null) {
menuEmpty.style.display = !hasEntries ? 'inline-block' : 'none';
}
@@ -67,42 +58,52 @@ export default class KimaiActiveRecords extends KimaiPlugin {
setEntries(entries) {
this._toggleMenu(entries.length > 0);
const template = this.menu.querySelector('[data-template="active-record"]');
const label = this.menu.querySelector('a > span.label');
if (label !== null) {
label.innerText = entries.length === 0 ? '' : entries.length;
}
if (entries.length === 0) {
this.label.innerText = '';
this.emptyList();
return;
}
let htmlToInsert = '';
const durations = this.getContainer().getPlugin('timesheet-duration');
for (let timesheet of entries) {
htmlToInsert +=
`<li>` +
`<a href="${ this.attributes['href'].replace('000', timesheet.id) }" data-event="kimai.timesheetStop kimai.timesheetUpdate" class="api-link" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">` +
`<div class="pull-left">` +
`<i class="${ this.attributes['icon'] } fa-2x"></i>` +
`</div>` +
`<h4>` +
`<span>${ timesheet.activity.name }</span>` +
`<small>` +
`<span data-title="true" data-since="${ timesheet.begin }">${ durations.formatDuration(timesheet.duration) }</span>` +
`</small>` +
`</h4>` +
`<p>${ timesheet.project.name } (${ timesheet.project.customer.name })</p>` +
`</a>` +
`</li>`;
}
if (this.label.dataset.warning < entries.length) {
this.label.classList = 'label label-danger';
if (template === null) {
this._replaceInNode(this.menu, entries[0]);
} else {
this.label.classList = 'label label-warning';
}
this.label.innerText = entries.length;
this.itemList.innerHTML = htmlToInsert;
const container = template.parentElement;
container.innerHTML = '';
durations.updateRecords();
for (let timesheet of entries) {
const newNode = template.cloneNode(true);
container.appendChild(this._replaceInNode(newNode, timesheet));
}
}
this.getContainer().getPlugin('timesheet-duration').updateRecords();
}
_replaceInNode(node, timesheet) {
const date = this.getContainer().getPlugin('date');
const allReplacer = node.querySelectorAll('[data-replacer]');
for (let node of allReplacer) {
const replacerName = node.dataset['replacer'];
if (replacerName === 'url') {
node.href = this.attributes['href'].replace('000', timesheet.id);
} else if (replacerName === 'activity') {
node.innerText = timesheet.activity.name;
} else if (replacerName === 'project') {
node.innerText = timesheet.project.name;
} else if (replacerName === 'customer') {
node.innerText = timesheet.project.customer.name;
} else if (replacerName === 'duration') {
node.dataset['since'] = timesheet.begin;
node.innerText = date.formatDuration(timesheet.duration);
}
}
return node;
}
reloadActiveRecords() {

View File

@@ -31,36 +31,28 @@ export default class KimaiAjaxModalForm extends KimaiReducedClickHandler {
this.isDirty = false;
this.modal = jQuery('#remote_form_modal');
this.modal.on('hide.bs.modal', function (e) {
if (self.isDirty) {
if (jQuery('#remote_form_modal .modal-body .remote_modal_is_dirty_warning').length === 0) {
const msg = self.getContainer().getTranslation().get('modal.dirty');
jQuery('#remote_form_modal .modal-body').prepend('<p class="'+(self.modal.hasClass('modal-danger') ? 'well well-sm ' : '') + 'text-danger small remote_modal_is_dirty_warning">' + msg + '</p>');
this.modal
.on('hide.bs.modal', function (e) {
if (self.isDirty) {
if (jQuery('#remote_form_modal .modal-body .remote_modal_is_dirty_warning').length === 0) {
const msg = self.getContainer().getTranslation().get('modal.dirty');
jQuery('#remote_form_modal .modal-body').prepend('<p class="'+(self.modal.hasClass('modal-danger') ? 'well well-sm ' : '') + 'text-danger small remote_modal_is_dirty_warning">' + msg + '</p>');
}
e.preventDefault();
return;
}
e.preventDefault();
return;
}
jQuery(self._getFormIdentifier()).off('change', self._isDirtyHandler);
self.isDirty = false;
self.getContainer().getPlugin('event').trigger('modal-hide');
});
this.modal.on('hidden.bs.modal', function () {
// kill all references, so GC can kick in
self.getContainer().getPlugin('form').destroyForm(self._getFormIdentifier());
jQuery('#remote_form_modal .modal-body').replaceWith('');
});
this.modal.on('show.bs.modal', function () {
self.getContainer().getPlugin('event').trigger('modal-show');
});
this.modal.on('shown.bs.modal', function () {
// workaround for autofocus attribute, as the modal "steals" it
let formAutofocus = jQuery(self._getFormIdentifier()).find('[autofocus]');
if (formAutofocus.length < 1) {
formAutofocus = jQuery(self._getFormIdentifier()).find('input[type=text],textarea,select');
}
formAutofocus.filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
jQuery(self._getFormIdentifier()).off('change', self._isDirtyHandler);
self.isDirty = false;
self.getContainer().getPlugin('event').trigger('modal-hide');
})
.on('hidden.bs.modal', function () {
// kill all references, so GC can kick in
self.getContainer().getPlugin('form').destroyForm(self._getFormIdentifier());
jQuery('#remote_form_modal .modal-body').replaceWith('');
})
.on('show.bs.modal', function () {
self.getContainer().getPlugin('event').trigger('modal-show');
});
this._addClickHandler(this.selector, function(href) {
self.openUrlInModal(href);

View File

@@ -24,6 +24,30 @@ export default class KimaiThemeInitializer extends KimaiPlugin {
// activate all form plugins
this.getContainer().getPlugin('form').activateForm('.content-wrapper form', 'body');
this.getContainer().getPlugin('form').activateForm('form.searchform', 'body');
this.registerModalAutofocus('#modal_search');
this.registerModalAutofocus('#remote_form_modal');
}
/**
* workaround for autofocus attribute, as the modal "steals" it
*
* @param {string} selector
*/
registerModalAutofocus(selector) {
let modal = jQuery(selector);
if (modal.length === 0) {
return;
}
modal.on('shown.bs.modal', function () {
let form = modal.find('form');
let formAutofocus = form.find('[autofocus]');
if (formAutofocus.length < 1) {
formAutofocus = form.find('input[type=text],textarea,select');
}
formAutofocus.filter(':not("[data-datetimepicker=on]")').filter(':visible:first').focus().delay(1000).focus();
});
}
/**

View File

@@ -15,43 +15,21 @@ $avatar-base-size: 30;
line-height: 1;
position: relative;
font-weight: bold;
font-size: ($avatar-base-size / 2 - 2)+px;
top: ($avatar-base-size / 4 - 1)+px;
font-size: ceil($avatar-base-size / 2 - 2)+px;
top: ceil($avatar-base-size / 4 - 1)+px;
}
.avatar-xs {
width: ($avatar-base-size * 0.75)+px;
height: ($avatar-base-size * 0.75)+px;
.initials {
font-size: ($avatar-base-size * .75 / 2 - 2)+px;
top: ($avatar-base-size * .75 / 4 - 1)+px;
}
}
$avatarSizes: "xs" .75, "sm" 1.25, "md" 1.5, "lg" 2;
.avatar-sm {
width: ($avatar-base-size * 1.25)+px;
height: ($avatar-base-size * 1.25)+px;
.initials {
font-size: ($avatar-base-size * 1.25 / 2 - 2)+px;
top: ($avatar-base-size * 1.25 / 4 - 1)+px;
}
}
@each $avatarSizeName, $avatarBaseSize in $avatarSizes {
.avatar-#{$avatarSizeName} {
width: ceil($avatar-base-size * $avatarBaseSize)+px;
height: ceil($avatar-base-size * $avatarBaseSize)+px;
.avatar-md {
width: ($avatar-base-size * 1.5)+px;
height: ($avatar-base-size * 1.5)+px;
.initials {
font-size: ($avatar-base-size * 1.5 / 2 - 2)+px;
top: ($avatar-base-size * 1.5 / 4 - 1)+px;
}
}
.avatar-lg {
width: ($avatar-base-size * 2)+px;
height: ($avatar-base-size * 2)+px;
.initials {
font-size: ($avatar-base-size * 2 / 2 - 2)+px;
top: ($avatar-base-size * 2 / 4 - 1)+px;
.initials {
font-size: ceil($avatar-base-size * $avatarBaseSize / 2 - 2)+px;
top: ceil($avatar-base-size * $avatarBaseSize / 4 - 1)+px;
}
}
}
@@ -59,11 +37,11 @@ $avatar-base-size: 30;
border-radius: 50%;
border: 3px solid #fff;
.avatar {
width: ($avatar-base-size * 2.75)+px;
height: ($avatar-base-size * 2.75)+px;
width: ceil($avatar-base-size * 2.75)+px;
height: ceil($avatar-base-size * 2.75)+px;
.initials {
font-size: ($avatar-base-size * 2.75 / 2 - 2)+px;
top: ($avatar-base-size * 2.75 / 4 - 1)+px;
font-size: ceil($avatar-base-size * 2.75 / 2 - 2)+px;
top: ceil($avatar-base-size * 2.75 / 4 - 1)+px;
}
}
}

View File

@@ -32,6 +32,9 @@
}
@media (max-width: $screen-md-min) {
.content-header h1 {
font-size: 1.5em;
}
.content-header>.breadcrumb {
margin-top: 0;
}

View File

@@ -5,22 +5,34 @@
* file that was distributed with this source code.
*/
.navbar-custom-menu {
.navbar-nav {
li>.ddt-small {
padding-top: 10px;
padding-bottom: 10px;
}
}
}
.navbar-nav {
img.img-circle {
max-height: 26px;
margin-top: -2px;
}
.avatar {
max-height: 26px;
max-width: 26px;
margin-top: -2px;
max-height: 30px;
max-width: 30px;
height: 30px;
width: 30px;
margin-top: 0;
.initials {
top: 4px;
top: 5px;
left: 0;
}
}
li>a.ddt-large {
padding: 15px 12px 11px 10px;
font-size: 10px;
li>.ddt-small {
padding-top: 10px;
padding-bottom: 10px;
}
li.messages-menu ul.menu li {
a:hover .pull-left i {
@@ -43,17 +55,13 @@
width: unset !important;
}
.user-menu {
margin-right: 10px;
ul {
li {
a {
color: #444;
&:hover {
background-color: #f4f4f4;
i {
color: $blue;
}
}
color: #444;
i {
padding-right: 10px;
}
@@ -64,9 +72,6 @@
}
}
}
.notifications-menu>.dropdown-menu>li .menu>li>a:hover i {
color: $green;
}
}
@media (max-width: $screen-sm-min) {
@@ -80,25 +85,69 @@
padding: 5px 25px 5px 25px
}
}
/*
.ticktac:hover i.running{
color: #4ff131;
@keyframes ticktac-blink {
0% { opacity: 1; }
5% { opacity: 0.95; }
10% { opacity: 0.9; }
15% { opacity: 0.85; }
20% { opacity: 0.8; }
25% { opacity: 0.75; }
30% { opacity: 0.7; }
35% { opacity: 0.65; }
40% { opacity: 0.6; }
45% { opacity: 0.65; }
50% { opacity: 0.7; }
55% { opacity: 0.75; }
60% { opacity: 0.8; }
65% { opacity: 0.85; }
70% { opacity: 0.9; }
75% { opacity: 0.95; }
100% { opacity: 1; }
}
.ticktac-single {
display: block;
position: relative;
padding-left: 15px;
padding-right: 15px;
i {
animation: ticktac-blink 2s step-end infinite;
}
span {
font-size: 1.8em;
line-height: 1em;
padding-left: 5px;
padding-right: 0;
}
&:hover {
background: rgba(0,0,0,.3);
}
}
body.fixed {
.navbar-nav {
.user-menu {
margin-right: 10px;
}
}
}
li.open .ticktac i.running{
color: #4ff131;
@media (max-width: $screen-sm-min) {
.ticktac-single {
padding-left: 5px;
padding-right: 5px;
}
.navbar-custom-menu .navbar-nav li>.ddt-small {
padding-left: 5px;
padding-right: 5px;
}
}
.ticktac i.stopped{
color: #fff;
@media (max-width: $screen-md-min) {
.ticktac-single {
padding-left: 10px;
padding-right: 10px;
}
.navbar-custom-menu .navbar-nav li>.ddt-small {
padding-left: 10px;
padding-right: 10px;
}
}
.ticktac:hover i.stopped{
color: #4ff131;
}
.user-panel>.info {
left: 40px;
padding-top: 0px;
}
*/

View File

@@ -1,26 +1,69 @@
$skins: blue, blue, black, green, purple, red, yellow;
$skins:
"blue" $yellow #fff $yellow $red,
"black" $yellow #000 $green $red,
"green" $yellow #fff $yellow $red,
"purple" $yellow #fff $yellow $red,
"red" $yellow #fff $yellow $yellow,
"yellow" $blue #fff $green $red
;
@each $skinColor in $skins {
@media (max-width: $screen-sm-min) {
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
}
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a {
color: #444;
}
.skin-#{$skinColor} .main-header .navbar .dropdown-menu li a:hover {
background: #fff;
}
@each $skinColor, $navHighlight, $tickTacColor, $startButton, $stopButton in $skins {
@each $prefix in (".skin-#{$skinColor}", ".skin-#{$skinColor}-light") {
@media (max-width: $screen-sm-min) {
#{$prefix} {
.main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li.divider {
background-color: #ccc;
.main-header .navbar .dropdown-menu li a {
color: #444;
}
.main-header .navbar .dropdown-menu li a:hover {
background: #fff;
}
}
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a {
color: #444;
}
.skin-#{$skinColor}-light .main-header .navbar .dropdown-menu li a:hover {
background: #fff;
#{$prefix} {
.navbar-nav {
.user-menu {
ul {
li {
a {
&:hover {
i {
color: $navHighlight;
}
}
}
}
}
}
.notifications-menu > .dropdown-menu > li .menu > li > a:hover i {
color: $navHighlight;
}
}
.ticktac-single {
i {
color: $tickTacColor;
}
span {
color: $tickTacColor;
}
&:hover {
.ticktac-stop {
i {
color: $stopButton;
}
}
.ticktac-start {
i {
color: $startButton;
}
}
}
}
}
}
}

View File

@@ -25,7 +25,7 @@ $green: #00a65a;
// Info
//$aqua: #00c0ef;
// Warning
//$yellow: #f39c12;
$yellow: #f0ad4e;
$blue: #0073b7;
//$navy: #001F3F;
//$teal: #39CCCC;