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:
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@
|
||||
}
|
||||
|
||||
@media (max-width: $screen-md-min) {
|
||||
.content-header h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
.content-header>.breadcrumb {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ $green: #00a65a;
|
||||
// Info
|
||||
//$aqua: #00c0ef;
|
||||
// Warning
|
||||
//$yellow: #f39c12;
|
||||
$yellow: #f0ad4e;
|
||||
$blue: #0073b7;
|
||||
//$navy: #001F3F;
|
||||
//$teal: #39CCCC;
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
public/build/app.65f97645.js
Normal file
2
public/build/app.65f97645.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,10 +3,10 @@
|
||||
"app": {
|
||||
"js": [
|
||||
"build/runtime.b8e7bb04.js",
|
||||
"build/app.07c264a7.js"
|
||||
"build/app.65f97645.js"
|
||||
],
|
||||
"css": [
|
||||
"build/app.7aa10842.css"
|
||||
"build/app.beac1734.css"
|
||||
]
|
||||
},
|
||||
"invoice": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"build/app.css": "build/app.7aa10842.css",
|
||||
"build/app.js": "build/app.07c264a7.js",
|
||||
"build/app.css": "build/app.beac1734.css",
|
||||
"build/app.js": "build/app.65f97645.js",
|
||||
"build/invoice.css": "build/invoice.ff32661a.css",
|
||||
"build/invoice.js": "build/invoice.19f36eca.js",
|
||||
"build/invoice-pdf.css": "build/invoice-pdf.9a7468ef.css",
|
||||
|
||||
@@ -24,11 +24,6 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
/** @var Timesheet $timesheet */
|
||||
$timesheet = $payload['timesheet'];
|
||||
if ($timesheet->getId() !== null) {
|
||||
if ($this->isGranted('edit', $timesheet)) {
|
||||
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('edit', ['url' => $this->path($routeEdit, ['id' => $timesheet->getId()]), 'class' => $class]);
|
||||
}
|
||||
|
||||
if ($timesheet->isRunning() && $this->isGranted('stop', $timesheet)) {
|
||||
$event->addAction('stop', ['url' => $this->path('stop_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-event' => 'kimai.timesheetStop kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.stop.error', 'data-msg-success' => 'timesheet.stop.success']]);
|
||||
}
|
||||
@@ -37,6 +32,11 @@ abstract class AbstractTimesheetSubscriber extends AbstractActionsSubscriber
|
||||
$event->addAction('repeat', ['url' => $this->path('restart_timesheet', ['id' => $timesheet->getId()]), 'class' => 'api-link', 'attr' => ['data-payload' => '{"copy": "all"}', 'data-event' => 'kimai.timesheetStart kimai.timesheetUpdate', 'data-method' => 'PATCH', 'data-msg-error' => 'timesheet.start.error', 'data-msg-success' => 'timesheet.start.success']]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('edit', $timesheet)) {
|
||||
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('edit', ['url' => $this->path($routeEdit, ['id' => $timesheet->getId()]), 'class' => $class]);
|
||||
}
|
||||
|
||||
if ($this->isGranted('duplicate', $timesheet)) {
|
||||
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
|
||||
$event->addAction('copy', ['url' => $this->path($routeDuplicate, ['id' => $timesheet->getId()]), 'class' => $class]);
|
||||
|
||||
@@ -88,82 +88,100 @@
|
||||
<!-- Page rendered on {{ 'now'|date_full }} -->
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_start %}
|
||||
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %}
|
||||
{% if is_granted('view_own_timesheet') %}
|
||||
{#
|
||||
{% block navbar_toggle %}
|
||||
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
|
||||
<span class="sr-only">{{ 'Toggle navigation'|trans({}, 'AdminLTEBundle') }}</span>
|
||||
</a>
|
||||
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') and is_granted('view_own_timesheet') %}
|
||||
<div class="navbar-custom-menu" style="float:left">
|
||||
<ul class="nav navbar-nav">
|
||||
{% if app.user.preferenceValue('login.initial_view') == 'calendar' %}
|
||||
<li class="visible-xs-inline-block">
|
||||
<a href="{{ path('calendar') }}" class="ddt-large">
|
||||
<li class="pull-left visible-xs-inline-block">
|
||||
<a href="{{ path('calendar') }}" class="ddt-small">
|
||||
<i class="{{ 'calendar'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="visible-xs-inline-block">
|
||||
<a href="{{ path('timesheet') }}" class="ddt-large">
|
||||
<li class="pull-left visible-xs-inline-block">
|
||||
<a href="{{ path('timesheet') }}" class="ddt-small">
|
||||
<i class="{{ 'timesheet'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_invoice') %}
|
||||
<li class="visible-xs-inline-block">
|
||||
<a href="{{ path('invoice') }}" class="ddt-large">
|
||||
<i class="{{ 'invoice'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('create_export') %}
|
||||
<li class="visible-xs-inline-block">
|
||||
<a href="{{ path('export') }}" class="ddt-large">
|
||||
<i class="{{ 'export'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% block navbar_extensions %}{% endblock %}
|
||||
{% set active_timesheets = active_timesheets(app.user) %}
|
||||
{% set active_limit = kimai_config.timesheetActiveEntriesHardLimit %}
|
||||
<li class="dropdown messages-menu" style="{% if active_timesheets is empty %}display:none{% endif %}">
|
||||
<a href="#" class="dropdown-toggle ddt-large ticktac" data-toggle="dropdown">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
<span data-warning="{{ active_limit }}" class="label label-{% if active_timesheets|length > active_limit %}danger{% else %}warning{% endif %}">{% if active_timesheets|length > 0 %}{{ active_timesheets|length }}{% endif %}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu"
|
||||
data-api="{{ path('active_timesheet') }}"
|
||||
data-href="{{ path('stop_timesheet', {'id' : '000'}) }}"
|
||||
data-icon="{{ 'stop-small'|icon }}">
|
||||
<li class="header">
|
||||
{{ 'active.entries'|trans }}
|
||||
</li>
|
||||
<li>
|
||||
<ul class="menu">
|
||||
{% for entry in active_timesheets %}
|
||||
<li>
|
||||
<a href="{{ path('stop_timesheet', {'id' : entry.id}) }}" class="api-link" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">
|
||||
<div class="pull-left">
|
||||
<i class="{{ 'stop-small'|icon }} fa-2x"></i>
|
||||
</div>
|
||||
<h4>
|
||||
<span>{{ entry.activity.name }}</span>
|
||||
<small>
|
||||
<span data-title="true" data-since="{{ entry.begin.format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
<p>{{ entry.project.name }} ({{ entry.project.customer.name }})</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% if is_granted('create_own_timesheet') %}
|
||||
<li class="footer"><a href="{{ path('timesheet_create') }}" class="modal-ajax-form">{{ 'timesheet.start'|trans }}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
#}
|
||||
|
||||
{% block navbar_start %}
|
||||
{% if app.user is not null and is_granted('IS_AUTHENTICATED_REMEMBERED') %}
|
||||
{% block navbar_extensions %}{% endblock %}
|
||||
{% if is_granted('create_own_timesheet') %}
|
||||
<li class="messages-menu-empty" style="{% if active_timesheets is not empty %}display:none{% endif %}">
|
||||
<a href="{{ path('timesheet_create') }}" class="ddt-large modal-ajax-form">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
{% set active_timesheets = active_timesheets(app.user) %}
|
||||
{% set hasActiveRecords = active_timesheets is not empty %}
|
||||
{% if not hasActiveRecords %}
|
||||
{# fake entry, because at least one html template node is needed #}
|
||||
{% set active_timesheets = [{'id': '000', 'begin': create_date('now'), 'activity': {'name': ''}, 'project': {'name': '', 'customer': {'name': ''}}}] %}
|
||||
{% endif %}
|
||||
{% set active_limit = kimai_config.timesheetActiveEntriesHardLimit %}
|
||||
<li class="messages-menu {% if active_limit > 1 and hasActiveRecords %}dropdown{% endif %}" data-api="{{ path('active_timesheet') }}" data-href="{{ path('stop_timesheet', {'id' : '000'}) }}" data-icon="{{ 'stop-small'|icon }}" style="{% if not hasActiveRecords %}display:none{% endif %}">
|
||||
{% if active_limit == 1 %}
|
||||
{% set entry = active_timesheets[0] %}
|
||||
<div class="ddt-small ticktac-single ticktac-running">
|
||||
<div class="ticktac-stop">
|
||||
<a data-replacer="url" class="api-link" href="{{ path('stop_timesheet', {'id' : entry.id}) }}" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">
|
||||
<i class="{{ 'stop-small'|icon }} fa-2x"></i><span data-replacer="duration" data-title="true" data-since="{{ entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="#" class="dropdown-toggle ddt-small ticktac" data-toggle="dropdown">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
<span data-warning="{{ active_limit }}" class="label label-warning">{% if hasActiveRecords %}{{ active_timesheets|length }}{% endif %}</span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="header">
|
||||
{{ 'active.entries'|trans }}
|
||||
</li>
|
||||
<li>
|
||||
<ul class="menu">
|
||||
{% for entry in active_timesheets %}
|
||||
<li data-template="active-record">
|
||||
<a data-replacer="url" href="{{ path('stop_timesheet', {'id' : entry.id}) }}" class="api-link" data-event="kimai.timesheetStop kimai.timesheetUpdate" data-method="PATCH" data-msg-error="timesheet.stop.error" data-msg-success="timesheet.stop.success">
|
||||
<div class="pull-left">
|
||||
<i class="{{ 'stop-small'|icon }} fa-2x"></i>
|
||||
</div>
|
||||
<h4>
|
||||
<span data-replacer="activity">{{ entry.activity.name }}</span>
|
||||
<small>
|
||||
<span data-replacer="duration" data-title="true" data-since="{{ entry.begin|date_format(constant('DATE_ISO8601')) }}">{{ entry|duration }}</span>
|
||||
</small>
|
||||
</h4>
|
||||
<p><span data-replacer="project">{{ entry.project.name }}</span> (<span data-replacer="customer">{{ entry.project.customer.name }}</span>)</p>
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="footer"><a href="{{ path('timesheet_create') }}" class="modal-ajax-form">{{ 'timesheet.start'|trans }}</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="messages-menu-empty" style="{% if hasActiveRecords %}display:none{% endif %}">
|
||||
{% if active_limit == 1 %}
|
||||
<div class="ddt-small ticktac-single ticktac-stopped">
|
||||
<a href="{{ path('timesheet_create') }}" class="modal-ajax-form ticktac-start">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
<span>{{ 0|duration }}</span>
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="{{ path('timesheet_create') }}" class="ddt-small ticktac-start modal-ajax-form">
|
||||
<i class="{{ 'start'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -188,7 +206,7 @@
|
||||
{% endif %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
<li class="dropdown user-menu">
|
||||
<a href="#" class="dropdown-toggle ddt-large" data-toggle="dropdown">
|
||||
<a href="#" class="dropdown-toggle ddt-small" data-toggle="dropdown">
|
||||
{{ widgets.user_avatar(app.user, false) }}
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{% import "invoice/macros.html.twig" as macros %}
|
||||
|
||||
{% set columns = {
|
||||
'date': {'class': 'alwaysVisible w-min', 'orderBy': false},
|
||||
'date': {'class': 'alwaysVisible', 'orderBy': false},
|
||||
'user': {'class': 'hidden-xs hidden-sm text-nowrap hidden', 'orderBy': false},
|
||||
'customer': {'class': 'hidden-xs hidden-sm text-nowrap', 'orderBy': false},
|
||||
'invoice_number': {'class': 'hidden-xs hidden-sm text-center w-min', 'title': 'invoice.number'|trans, 'orderBy': false},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{% if is_granted('start_own_timesheet') %}
|
||||
<li class="dropdown notifications-menu">
|
||||
<a href="#" class="dropdown-toggle ddt-large" data-toggle="dropdown">
|
||||
<i class="{{ 'activity'|icon }} fa-2x"></i>
|
||||
<a href="#" class="dropdown-toggle ddt-small" data-toggle="dropdown">
|
||||
<i class="{{ 'repeat'|icon }} fa-2x"></i>
|
||||
</a>
|
||||
<ul class="dropdown-menu"
|
||||
data-api="{{ path('recent_timesheet', {'size' : 10}) }}"
|
||||
|
||||
@@ -82,8 +82,8 @@ class LayoutControllerTest extends ControllerBaseTest
|
||||
|
||||
$content = $client->getResponse()->getContent();
|
||||
|
||||
self::assertStringContainsString('<li class="dropdown messages-menu" style="display:none">', $content);
|
||||
self::assertStringContainsString('<ul class="dropdown-menu"', $content);
|
||||
self::assertStringContainsString('<li class="messages-menu', $content);
|
||||
self::assertStringContainsString('<div class="ddt-small ticktac-single ', $content);
|
||||
self::assertStringContainsString('data-api="', $content);
|
||||
self::assertStringContainsString('data-href="', $content);
|
||||
self::assertStringContainsString('data-icon=', $content);
|
||||
|
||||
Reference in New Issue
Block a user