From db7de3aac1899a25a9a491c38f7fc42645708272 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Tue, 6 Nov 2018 23:10:34 +0100 Subject: [PATCH] added global activities (#259) --- README.md | 2 +- assets/css/app.scss | 165 ++++++++++++++++++ assets/js/kimai.js | 22 +++ assets/js/toolbar.js | 3 + config/packages/fos_rest.yaml | 6 +- public/build/app.js | 2 +- public/build/manifest.json | 2 +- src/API/ActivityController.php | 26 ++- src/API/ProjectController.php | 21 ++- src/Calendar/TimesheetEntity.php | 4 +- src/Controller/ActivityController.php | 5 +- src/Controller/TimesheetController.php | 28 ++- src/DataFixtures/CustomerFixtures.php | 16 +- src/DataFixtures/TimesheetFixtures.php | 25 ++- src/Entity/Activity.php | 1 - src/Entity/Project.php | 34 +++- src/Entity/Timesheet.php | 55 +++++- src/Form/ActivityEditForm.php | 21 ++- src/Form/TimesheetEditForm.php | 76 +++++++- src/Form/Toolbar/AbstractToolbarForm.php | 5 +- src/Form/Toolbar/TimesheetToolbarForm.php | 1 + .../ActivityGroupedWithCustomerNameType.php | 38 ---- src/Form/Type/ActivityType.php | 17 +- src/Form/Type/CustomerType.php | 1 + src/Form/Type/PageSizeType.php | 1 + src/Form/Type/ProjectType.php | 1 + src/Form/Type/VisibilityType.php | 1 + src/Migrations/Version20181031220003.php | 127 ++++++++++++++ src/Repository/ActivityRepository.php | 96 ++++++---- src/Repository/CustomerRepository.php | 2 +- src/Repository/ProjectRepository.php | 11 +- src/Repository/Query/ActivityQuery.php | 54 +++++- src/Repository/Query/BaseQuery.php | 4 +- src/Repository/Query/ProjectQuery.php | 8 +- src/Repository/TimesheetRepository.php | 45 ++--- src/Timesheet/Calculator/RateCalculator.php | 4 +- src/Voter/TimesheetVoter.php | 36 +--- templates/admin/activity.html.twig | 12 +- templates/admin/activity_delete.html.twig | 10 +- templates/admin/timesheet.html.twig | 2 +- .../SwaggerUI/index.html.twig | 18 ++ templates/form/kimai-theme.html.twig | 7 + templates/invoice/preview.html.twig | 2 +- templates/invoice/renderer/default.html.twig | 2 +- .../invoice/renderer/timesheet.html.twig | 2 +- templates/macros/widgets.html.twig | 15 +- templates/navbar/active-entries.html.twig | 2 +- templates/navbar/recent-activities.html.twig | 12 +- templates/timesheet/export.html.twig | 4 +- templates/timesheet/index.html.twig | 2 +- tests/API/APIControllerBaseTest.php | 5 +- tests/API/ActivityControllerTest.php | 79 ++++++++- tests/API/ProjectControllerTest.php | 78 ++++++++- .../Admin/TimesheetControllerTest.php | 4 +- tests/Controller/ControllerBaseTest.php | 14 +- tests/Controller/TimesheetControllerTest.php | 22 ++- tests/DataFixtures/TimesheetFixtures.php | 51 +++++- tests/Entity/AbstractEntityTest.php | 4 +- tests/Entity/ProjectTest.php | 6 + tests/Entity/TimesheetTest.php | 43 +++++ .../AbstractInvoiceCalculatorTest.php | 2 +- .../Calculator/ShortInvoiceCalculatorTest.php | 8 + .../Invoice/Renderer/AbstractRendererTest.php | 5 + tests/Repository/Query/ActivityQueryTest.php | 7 + tests/Repository/Query/ProjectQueryTest.php | 4 + tests/Repository/TimesheetRepositoryTest.php | 12 +- .../Calculator/RateCalculatorTest.php | 1 + tests/Voter/TimesheetVoterTest.php | 24 ++- var/data/kimai_test.sqlite | Bin 770048 -> 770048 bytes var/docs/faq.md | 7 + 70 files changed, 1168 insertions(+), 264 deletions(-) create mode 100644 assets/css/app.scss delete mode 100644 src/Form/Type/ActivityGroupedWithCustomerNameType.php create mode 100644 src/Migrations/Version20181031220003.php create mode 100644 templates/bundles/NelmioApiDocBundle/SwaggerUI/index.html.twig diff --git a/README.md b/README.md index c2db0b3a..8f151053 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ chmod -R g+rw var/ cp .env.dist .env ``` -It's up to you which database server you want to use, Kimai v2 supports MySQL/MariaDB and SQLite. +It's up to you which database server you want to use, Kimai v2 supports MySQL/MariaDB and SQLite, but SQLite is [not recommended](var/docs/faq.md) for production usage. Configure the database connection string in your the `.env` file: ``` # adjust all settings in .env to your needs diff --git a/assets/css/app.scss b/assets/css/app.scss new file mode 100644 index 00000000..424f186b --- /dev/null +++ b/assets/css/app.scss @@ -0,0 +1,165 @@ +/* + * This file is part of the Kimai time-tracking app. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +@import "~bootstrap-sass/assets/stylesheets/bootstrap/variables"; + +.error-page { + margin-bottom: 50px; +} + +/* ================================ CONTENT ================================ */ + +.content { + padding: 15px 0; +} +.content-header { + h1 { + small { + display: none; + } + } +} + +@media (min-width: $screen-sm-min) { + .content-header { + h1 { + small { + display: inline-block; + } + } + } + .content { + padding: 15px; + } +} + +/* ================================ NAVBAR ================================ */ + +.navbar-nav > li > a.ddt-large { + padding-top: 12px; + padding-bottom: 9px; +} + +li.messages-menu ul.menu li .pull-left i { + color: #dd4b39; +} + +.navbar-nav>.messages-menu>.dropdown-menu>li .menu>li>a>p { + overflow: hidden; + text-overflow: ellipsis; +} + +.navbar-nav .start_record { + text-align: center; +} + +/* +.ticktac:hover i.running{ + color: #4ff131; +} + +li.open .ticktac i.running{ + color: #4ff131; +} + +.ticktac i.stopped{ + color: #fff; +} + +.ticktac:hover i.stopped{ + color: #4ff131; +} +.user-panel>.info { + left: 40px; + padding-top: 0px; +} +*/ + +/* ================================ TOOLBAR ================================ */ + +/* Page based action buttons in the upper right corner of the content area */ +.content-header > .breadcrumb { + position: absolute; + float: right; + background: transparent; +} +/* Right sidebar with additional tabs for personal settings and "About Kimai" section */ +.control-sidebar { + select { + color: $input-color; + background-color: $input-bg; + } + div.image { + img.img-circle { + max-width: 34px; + } + } +} +/* The filter form is available on most pages above the datatable */ +@media (min-width: 768px) { + .toolbar { + form.navbar-form { + font-size: $font-size-base; + .form-control { + display: inline-block; + width: 100%; + vertical-align: middle; + } + } + } +} +/* ================================ SIDEBAR ================================ */ + +/* Used for the Sidebar UserPanel (which is deactivated right now) */ +.user-panel>.info { + left: 80px; +} + +/* ================================ FOOTER ================================ */ + +footer.main-footer { + padding: 5px; + font-size: 80%; +} + +/* ================================ PRINT ================================ */ + +@media print{ + .sf-toolbar, .control-sidebar {display: none !important;} +} + +/* ================================ INVOICES ================================ */ + +.invoice { + margin: 0; +} + +@media (min-width: $screen-sm-min) { + .invoice { + margin: 10px 25px; + } +} + +.page-header-small{ + font-size: 14px; +} + +div.invoice-address { + margin-top: 30px; + margin-bottom: 30px; +} + +table.invoice-records {} +table.invoice-meta th { + padding-right: 40px; +} +table.invoice-sum th { + width: 70%; +} +table.invoice-sum th, table.invoice-sum td { + text-align: right; +} diff --git a/assets/js/kimai.js b/assets/js/kimai.js index dfaa56c9..047baea7 100644 --- a/assets/js/kimai.js +++ b/assets/js/kimai.js @@ -84,6 +84,28 @@ $(function() { $(this).trigger("change"); }); + $('select[data-related-select]').change(function() { + var apiUrl = $(this).attr('data-api-url').replace('-s-', $(this).val()); + var targetSelect = $(this).attr('data-related-select'); + + $.ajax({ + url: apiUrl, + headers: { + 'X-AUTH-SESSION': true, + 'Content-Type':'application/json' + }, + method: 'GET', + dataType: 'json', + success: function(data){ + $('#' + targetSelect).find('option').remove().end().find('optgroup').remove().end(); + $.each(data, function(i, obj) { + $('#' + targetSelect).append(''); + }); + $('#' + targetSelect).trigger('change') + } + }); + }); + /* $('input').iCheck({ checkboxClass: 'icheckbox_square-blue', diff --git a/assets/js/toolbar.js b/assets/js/toolbar.js index f5224817..c54fe29f 100644 --- a/assets/js/toolbar.js +++ b/assets/js/toolbar.js @@ -18,14 +18,17 @@ $(document).ready(function () { case 'customer': if ($(this).val() === '') { $('.toolbar form select#project').parent().remove(); + //$('.toolbar form select#project').parent().parent().remove(); } else { $('.toolbar form select#project').val(''); } $('.toolbar form select#activity').parent().remove(); + //$('.toolbar form select#activity').parent().parent().remove(); break; case 'project': if ($(this).val() === '') { $('.toolbar form select#activity').parent().remove(); + //$('.toolbar form select#activity').parent().parent().remove(); } else { $('.toolbar form select#activity').val(''); } diff --git a/config/packages/fos_rest.yaml b/config/packages/fos_rest.yaml index bbcac497..015f043f 100644 --- a/config/packages/fos_rest.yaml +++ b/config/packages/fos_rest.yaml @@ -6,11 +6,11 @@ fos_rest: # formats: # name: ~ # unauthorized_challenge: null -# param_fetcher_listener: -# enabled: false + param_fetcher_listener: + enabled: true # force: false # service: null -# cache_dir: '%kernel.cache_dir%/fos_rest' + cache_dir: '%kernel.cache_dir%/fos_rest' # allowed_methods_listener: # enabled: false # service: null diff --git a/public/build/app.js b/public/build/app.js index 54b6e0de..6ef4bab9 100644 --- a/public/build/app.js +++ b/public/build/app.js @@ -70,7 +70,7 @@ if(void 0===e)throw new Error("AdminLTE requires jQuery");+function(e){"use stri * (c) 2013 Damir Sultanov, http://fronteed.com * MIT Licensed */ -!function(e){function t(e,t,a){var r=e[0],o=/er/.test(a)?f:/bl/.test(a)?m:c,s=a==_?{checked:r[c],disabled:r[m],indeterminate:"true"==e.attr(f)||"false"==e.attr(p)}:r[o];if(/^(ch|di|in)/.test(a)&&!s)n(e,o);else if(/^(un|en|de)/.test(a)&&s)i(e,o);else if(a==_)for(var d in s)s[d]?n(e,d,!0):i(e,d,!0);else t&&"toggle"!=a||(t||e[b]("ifClicked"),s?r[g]!==u&&i(e,o):n(e,o))}function n(t,n,a){var _=t[0],y=t.parent(),b=n==c,L=n==f,k=n==m,w=L?p:b?h:"enabled",T=r(t,w+o(_[g])),x=r(t,n+o(_[g]));if(!0!==_[n]){if(!a&&n==c&&_[g]==u&&_.name){var Y=t.closest("form"),S='input[name="'+_.name+'"]';S=Y.length?Y.find(S):e(S),S.each(function(){this!==_&&e(this).data(d)&&i(e(this),n)})}L?(_[n]=!0,_[c]&&i(t,c,"force")):(a||(_[n]=!0),b&&_[f]&&i(t,f,!1)),s(t,b,n,a)}_[m]&&r(t,D,!0)&&y.find("."+l).css(D,"default"),y[v](x||r(t,n)||""),y.attr("role")&&!L&&y.attr("aria-"+(k?m:c),"true"),y[M](T||r(t,w)||"")}function i(e,t,n){var i=e[0],a=e.parent(),d=t==c,u=t==f,_=t==m,y=u?p:d?h:"enabled",b=r(e,y+o(i[g])),L=r(e,t+o(i[g]));!1!==i[t]&&(!u&&n&&"force"!=n||(i[t]=!1),s(e,d,y,n)),!i[m]&&r(e,D,!0)&&a.find("."+l).css(D,"pointer"),a[M](L||r(e,t)||""),a.attr("role")&&!u&&a.attr("aria-"+(_?m:c),"false"),a[v](b||r(e,y)||"")}function a(t,n){t.data(d)&&(t.parent().html(t.attr("style",t.data(d).s||"")),n&&t[b](n),t.off(".i").unwrap(),e(L+'[for="'+t[0].id+'"]').add(t.closest(L)).off(".i"))}function r(e,t,n){if(e.data(d))return e.data(d).o[t+(n?"":"Class")]}function o(e){return e.charAt(0).toUpperCase()+e.slice(1)}function s(e,t,n,i){i||(t&&e[b]("ifToggled"),e[b]("ifChanged")[b]("if"+o(n)))}var d="iCheck",l=d+"-helper",u="radio",c="checked",h="un"+c,m="disabled",p="determinate",f="in"+p,_="update",g="type",y="touchbegin.i touchend.i",v="addClass",M="removeClass",b="trigger",L="label",D="cursor",k=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);e.fn[d]=function(r,o){var s='input[type="checkbox"], input[type="'+u+'"]',h=e(),p=function(t){t.each(function(){var t=e(this);h=t.is(s)?h.add(t):h.add(t.find(s))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(r))return r=r.toLowerCase(),p(this),h.each(function(){var n=e(this);"destroy"==r?a(n,"ifDestroyed"):t(n,!0,r),e.isFunction(o)&&o()});if("object"!=typeof r&&r)return this;var D=e.extend({checkedClass:c,disabledClass:m,indeterminateClass:f,labelHover:!0},r),w=D.handle,T=D.hoverClass||"hover",x=D.focusClass||"focus",Y=D.activeClass||"active",S=!!D.labelHover,H=D.labelHoverClass||"hover",C=0|(""+D.increaseArea).replace("%","");return"checkbox"!=w&&w!=u||(s='input[type="'+w+'"]'),C<-50&&(C=-50),p(this),h.each(function(){var r=e(this);a(r);var o,s=this,h=s.id,p=-C+"%",f=100+2*C+"%",w={position:"absolute",top:p,left:p,display:"block",width:f,height:f,margin:0,padding:0,background:"#fff",border:0,opacity:0},E=k?{position:"absolute",visibility:"hidden"}:C?w:{position:"absolute",opacity:0},P="checkbox"==s[g]?D.checkboxClass||"icheckbox":D.radioClass||"i"+u,j=e(L+'[for="'+h+'"]').add(r.closest(L)),O=!!D.aria,A=d+"-"+Math.random().toString(36).substr(2,6),I='
")[b]("ifCreated").parent().append(D.insert),o=e('').css(w).appendTo(I),r.data(d,{o:D,s:r.attr("style")}).css(E),!!D.inheritClass&&I[v](s.className||""),!!D.inheritID&&h&&I.attr("id",d+"-"+h),"static"==I.css("position")&&I.css("position","relative"),t(r,!0,_),j.length&&j.on("click.i mouseover.i mouseout.i "+y,function(n){var i=n[g],a=e(this);if(!s[m]){if("click"==i){if(e(n.target).is("a"))return;t(r,!1,!0)}else S&&(/ut|nd/.test(i)?(I[M](T),a[M](H)):(I[v](T),a[v](H)));if(!k)return!1;n.stopPropagation()}}),r.on("click.i focus.i blur.i keyup.i keydown.i keypress.i",function(e){var t=e[g],a=e.keyCode;return"click"!=t&&("keydown"==t&&32==a?(s[g]==u&&s[c]||(s[c]?i(r,c):n(r,c)),!1):void("keyup"==t&&s[g]==u?!s[c]&&n(r,c):/us|ur/.test(t)&&I["blur"==t?M:v](x)))}),o.on("click mousedown mouseup mouseover mouseout "+y,function(e){var n=e[g],i=/wn|up/.test(n)?Y:T;if(!s[m]){if("click"==n?t(r,!1,!0):(/wn|er|in/.test(n)?I[v](i):I[M](i+" "+Y),j.length&&S&&i==T&&j[/ut|nd/.test(n)?M:v](H)),!k)return!1;e.stopPropagation()}})})}}(e||window.Zepto)}).call(t,n("7t+N"))},aM0x:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn",{months:"জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|সকাল|দুপুর|বিকাল|রাত/,meridiemHour:function(e,t){return 12===e&&(e=0),"রাত"===t&&e>=4||"দুপুর"===t&&e<5||"বিকাল"===t?e+12:e},meridiem:function(e,t,n){return e<4?"রাত":e<10?"সকাল":e<17?"দুপুর":e<20?"বিকাল":"রাত"},week:{dow:0,doy:6}})})},aqvp:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n){var i=e+" ";switch(n){case"ss":return i+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return t?"jedna minuta":"jedne minute";case"mm":return i+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return t?"jedan sat":"jednog sata";case"hh":return i+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return i+=1===e?"dan":"dana";case"MM":return i+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return i+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}return e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},bXQP:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}})})},c0dd:function(e,t,n){n("chxu"),n("nyna"),n("olwm"),n("GOEF"),n("vg91"),n("AixB"),n("1QPC"),n("gcLw"),n("nRX6"),n("SAIs"),n("EmE7"),n("mN5h")},c1x4:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["секунда","секунде","секунди"],m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr-cyrl",{months:"јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар".split("_"),monthsShort:"јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.".split("_"),monthsParseExact:!0,weekdays:"недеља_понедељак_уторак_среда_четвртак_петак_субота".split("_"),weekdaysShort:"нед._пон._уто._сре._чет._пет._суб.".split("_"),weekdaysMin:"не_по_ут_ср_че_пе_су".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){return["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"дан",dd:t.translate,M:"месец",MM:t.translate,y:"годину",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},chxu:function(e,t,n){(function(e){var t=n("7t+N");e.$=e.jQuery=t,n("z1kw"),n("jf49"),n("DBzq"),n("tNVI");var i=n("PJh5");e.moment=i,n("PL/P"),n("WFwX"),n("R6CI"),n("iES9"),n("FDd9"),e.$.AdminLTE={},e.$.AdminLTE.options={},n("Dehm"),n("l77c"),n("a7jZ"),n("3EEf")}).call(t,n("DuR2"))},dURR:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}})})},dyB6:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"e/KL":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("x-pseudo",{months:"J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér".split("_"),monthsShort:"J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc".split("_"),monthsParseExact:!0,weekdays:"S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý".split("_"),weekdaysShort:"S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát".split("_"),weekdaysMin:"S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~ódá~ý át] LT",nextDay:"[T~ómó~rró~w át] LT",nextWeek:"dddd [át] LT",lastDay:"[Ý~ést~érdá~ý át] LT",lastWeek:"[L~ást] dddd [át] LT",sameElse:"L"},relativeTime:{future:"í~ñ %s",past:"%s á~gó",s:"á ~féw ~sécó~ñds",ss:"%d s~écóñ~ds",m:"á ~míñ~úté",mm:"%d m~íñú~tés",h:"á~ñ hó~úr",hh:"%d h~óúrs",d:"á ~dáý",dd:"%d d~áýs",M:"á ~móñ~th",MM:"%d m~óñt~hs",y:"á ~ýéár",yy:"%d ý~éárs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"eBB/":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",ss:"%d초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"},dayOfMonthOrdinalParse:/\d{1,2}(일|월|주)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"일";case"M":return e+"월";case"w":case"W":return e+"주";default:return e}},meridiemParse:/오전|오후/,isPM:function(e){return"오후"===e},meridiem:function(e,t,n){return e<12?"오전":"오후"}})})},eHwN:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-üncü",4:"-üncü",100:"-üncü",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"),weekdaysMin:"Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[dünən] LT",lastWeek:"[keçən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"birneçə saniyə",ss:"%d saniyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gecə|səhər|gündüz|axşam/,isPM:function(e){return/^(gündüz|axşam)$/.test(e)},meridiem:function(e,t,n){return e<4?"gecə":e<12?"səhər":e<17?"gündüz":"axşam"},dayOfMonthOrdinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(e){if(0===e)return e+"-ıncı";var n=e%10,i=e%100-n,a=e>=100?100:null;return e+(t[n]||t[i]||t[a])},week:{dow:1,doy:7}})})},f4W3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},fW1y:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t=["جنوري","فيبروري","مارچ","اپريل","مئي","جون","جولاءِ","آگسٽ","سيپٽمبر","آڪٽوبر","نومبر","ڊسمبر"],n=["آچر","سومر","اڱارو","اربع","خميس","جمع","ڇنڇر"];return e.defineLocale("sd",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[اڄ] LT",nextDay:"[سڀاڻي] LT",nextWeek:"dddd [اڳين هفتي تي] LT",lastDay:"[ڪالهه] LT",lastWeek:"[گزريل هفتي] dddd [تي] LT",sameElse:"L"},relativeTime:{future:"%s پوء",past:"%s اڳ",s:"چند سيڪنڊ",ss:"%d سيڪنڊ",m:"هڪ منٽ",mm:"%d منٽ",h:"هڪ ڪلاڪ",hh:"%d ڪلاڪ",d:"هڪ ڏينهن",dd:"%d ڏينهن",M:"هڪ مهينو",MM:"%d مهينا",y:"هڪ سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})},g7KF:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),n="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");return e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,i){return e?/-MMM-/.test(i)?n[e.month()]:t[e.month()]:t},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[ôfrûne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien minút",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})},gEQe:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"೧",2:"೨",3:"೩",4:"೪",5:"೫",6:"೬",7:"೭",8:"೮",9:"೯",0:"೦"},n={"೧":"1","೨":"2","೩":"3","೪":"4","೫":"5","೬":"6","೭":"7","೮":"8","೯":"9","೦":"0"};return e.defineLocale("kn",{months:"ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್".split("_"),monthsShort:"ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ".split("_"),monthsParseExact:!0,weekdays:"ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ".split("_"),weekdaysShort:"ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ".split("_"),weekdaysMin:"ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[ಇಂದು] LT",nextDay:"[ನಾಳೆ] LT",nextWeek:"dddd, LT",lastDay:"[ನಿನ್ನೆ] LT",lastWeek:"[ಕೊನೆಯ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ನಂತರ",past:"%s ಹಿಂದೆ",s:"ಕೆಲವು ಕ್ಷಣಗಳು",ss:"%d ಸೆಕೆಂಡುಗಳು",m:"ಒಂದು ನಿಮಿಷ",mm:"%d ನಿಮಿಷ",h:"ಒಂದು ಗಂಟೆ",hh:"%d ಗಂಟೆ",d:"ಒಂದು ದಿನ",dd:"%d ದಿನ",M:"ಒಂದು ತಿಂಗಳು",MM:"%d ತಿಂಗಳು",y:"ಒಂದು ವರ್ಷ",yy:"%d ವರ್ಷ"},preparse:function(e){return e.replace(/[೧೨೩೪೫೬೭೮೯೦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/,meridiemHour:function(e,t){return 12===e&&(e=0),"ರಾತ್ರಿ"===t?e<4?e:e+12:"ಬೆಳಿಗ್ಗೆ"===t?e:"ಮಧ್ಯಾಹ್ನ"===t?e>=10?e:e+12:"ಸಂಜೆ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ರಾತ್ರಿ":e<10?"ಬೆಳಿಗ್ಗೆ":e<17?"ಮಧ್ಯಾಹ್ನ":e<20?"ಸಂಜೆ":"ರಾತ್ರಿ"},dayOfMonthOrdinalParse:/\d{1,2}(ನೇ)/,ordinal:function(e){return e+"ನೇ"},week:{dow:0,doy:6}})})},gEU3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("mi",{months:"Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei".split("_"),weekdaysShort:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),weekdaysMin:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te hēkona ruarua",ss:"%d hēkona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})})},gUgh:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Juñu_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"minutu balun",ss:"minutu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},gcLw:function(e,t,n){(function(e,t){if(void 0===e)throw new Error("Kimai requires jQuery");t.kimai={},t(function(){"use strict";t.kimai={init:function(e){void 0!==e&&(t.kimai.settings=t.extend({},t.kimai.defaults,e)),moment.locale(t.kimai.settings.locale),t("a.btn-trash").click(function(e){return confirm(t.kimai.settings.confirmDelete)}),t(".dropdown-toggle").dropdown(),t.kimai.settings.alertSuccessAutoHide>0&&setTimeout(function(){t("div.alert-success").alert("close")},t.kimai.settings.alertSuccessAutoHide),t('input[data-datepicker="on"]').daterangepicker({singleDatePicker:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:"YYYY-MM-DD",firstDay:1}}),t('input[data-datepicker="on"]').on("apply.daterangepicker",function(e,n){t(this).val(n.startDate.format("YYYY-MM-DD")),t(this).trigger("change")}),t('input[data-datetimepicker="on"]').daterangepicker({singleDatePicker:!0,timePicker:!0,timePicker24Hour:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{cancelLabel:"Clear",format:"YYYY-MM-DD HH:mm",firstDay:1}}),t('input[data-datetimepicker="on"]').on("apply.daterangepicker",function(e,n){t(this).val(n.startDate.format("YYYY-MM-DD HH:mm")),t(this).trigger("change")})},pauseRecord:function(e){t(e+" .pull-left i").hover(function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/stop","/pause")),t(this).removeClass("fa-stop-circle").addClass("fa-pause-circle").addClass("text-orange")},function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/pause","/stop")),t(this).removeClass("fa-pause-circle").removeClass("text-orange").addClass("fa-stop-circle")})}},t.kimai.defaults={locale:"en",confirmDelete:"Really delete?",alertSuccessAutoHide:5e3},t.kimai.settings={}})}).call(t,n("7t+N"),n("7t+N"))},hPuz:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},hng5:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des".split("_"),weekdays:"Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm"},calendar:{sameDay:"[Bi lɛrɛ] LT",nextDay:"[Sini lɛrɛ] LT",nextWeek:"dddd [don lɛrɛ] LT",lastDay:"[Kunu lɛrɛ] LT",lastWeek:"dddd [tɛmɛnen lɛrɛ] LT",sameElse:"L"},relativeTime:{future:"%s kɔnɔ",past:"a bɛ %s bɔ",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"lɛrɛ kelen",hh:"lɛrɛ %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}})})},iES9:function(e,t){},iNtv:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n,i){var a={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n míut","'iens míut"],mm:[e+" míuts",e+" míuts"],h:["'n þora","'iensa þora"],hh:[e+" þoras",e+" þoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return i?a[n][0]:t?a[n][0]:a[n][1]}return e.defineLocale("tzl",{months:"Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi".split("_"),weekdaysShort:"Súl_Lún_Mai_Már_Xhú_Vié_Sát".split("_"),weekdaysMin:"Sú_Lú_Ma_Má_Xh_Vi_Sá".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,t,n){return e>11?n?"d'o":"D'O":n?"d'a":"D'A"},calendar:{sameDay:"[oxhi à] LT",nextDay:"[demà à] LT",nextWeek:"dddd [à] LT",lastDay:"[ieiri à] LT",lastWeek:"[sür el] dddd [lasteu à] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})},"j+vx":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={0:"-ші",1:"-ші",2:"-ші",3:"-ші",4:"-ші",5:"-ші",6:"-шы",7:"-ші",8:"-ші",9:"-шы",10:"-шы",20:"-шы",30:"-шы",40:"-шы",50:"-ші",60:"-шы",70:"-ші",80:"-ші",90:"-шы",100:"-ші"};return e.defineLocale("kk",{months:"қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан".split("_"),monthsShort:"қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел".split("_"),weekdays:"жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі".split("_"),weekdaysShort:"жек_дүй_сей_сәр_бей_жұм_сен".split("_"),weekdaysMin:"жк_дй_сй_ср_бй_жм_сн".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгін сағат] LT",nextDay:"[Ертең сағат] LT",nextWeek:"dddd [сағат] LT",lastDay:"[Кеше сағат] LT",lastWeek:"[Өткен аптаның] dddd [сағат] LT",sameElse:"L"},relativeTime:{future:"%s ішінде",past:"%s бұрын",s:"бірнеше секунд",ss:"%d секунд",m:"бір минут",mm:"%d минут",h:"бір сағат",hh:"%d сағат",d:"бір күн",dd:"%d күн",M:"бір ай",MM:"%d ай",y:"бір жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(ші|шы)/,ordinal:function(e){var n=e%10,i=e>=100?100:null;return e+(t[e]||t[n]||t[i])},week:{dow:1,doy:7}})})},j8cJ:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-kw",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:0,doy:12}})})},jf49:function(e,t,n){(function(e){/*! +!function(e){function t(e,t,a){var r=e[0],o=/er/.test(a)?f:/bl/.test(a)?m:c,s=a==_?{checked:r[c],disabled:r[m],indeterminate:"true"==e.attr(f)||"false"==e.attr(p)}:r[o];if(/^(ch|di|in)/.test(a)&&!s)n(e,o);else if(/^(un|en|de)/.test(a)&&s)i(e,o);else if(a==_)for(var d in s)s[d]?n(e,d,!0):i(e,d,!0);else t&&"toggle"!=a||(t||e[b]("ifClicked"),s?r[g]!==u&&i(e,o):n(e,o))}function n(t,n,a){var _=t[0],y=t.parent(),b=n==c,L=n==f,k=n==m,w=L?p:b?h:"enabled",T=r(t,w+o(_[g])),x=r(t,n+o(_[g]));if(!0!==_[n]){if(!a&&n==c&&_[g]==u&&_.name){var Y=t.closest("form"),S='input[name="'+_.name+'"]';S=Y.length?Y.find(S):e(S),S.each(function(){this!==_&&e(this).data(d)&&i(e(this),n)})}L?(_[n]=!0,_[c]&&i(t,c,"force")):(a||(_[n]=!0),b&&_[f]&&i(t,f,!1)),s(t,b,n,a)}_[m]&&r(t,D,!0)&&y.find("."+l).css(D,"default"),y[v](x||r(t,n)||""),y.attr("role")&&!L&&y.attr("aria-"+(k?m:c),"true"),y[M](T||r(t,w)||"")}function i(e,t,n){var i=e[0],a=e.parent(),d=t==c,u=t==f,_=t==m,y=u?p:d?h:"enabled",b=r(e,y+o(i[g])),L=r(e,t+o(i[g]));!1!==i[t]&&(!u&&n&&"force"!=n||(i[t]=!1),s(e,d,y,n)),!i[m]&&r(e,D,!0)&&a.find("."+l).css(D,"pointer"),a[M](L||r(e,t)||""),a.attr("role")&&!u&&a.attr("aria-"+(_?m:c),"false"),a[v](b||r(e,y)||"")}function a(t,n){t.data(d)&&(t.parent().html(t.attr("style",t.data(d).s||"")),n&&t[b](n),t.off(".i").unwrap(),e(L+'[for="'+t[0].id+'"]').add(t.closest(L)).off(".i"))}function r(e,t,n){if(e.data(d))return e.data(d).o[t+(n?"":"Class")]}function o(e){return e.charAt(0).toUpperCase()+e.slice(1)}function s(e,t,n,i){i||(t&&e[b]("ifToggled"),e[b]("ifChanged")[b]("if"+o(n)))}var d="iCheck",l=d+"-helper",u="radio",c="checked",h="un"+c,m="disabled",p="determinate",f="in"+p,_="update",g="type",y="touchbegin.i touchend.i",v="addClass",M="removeClass",b="trigger",L="label",D="cursor",k=/ipad|iphone|ipod|android|blackberry|windows phone|opera mini|silk/i.test(navigator.userAgent);e.fn[d]=function(r,o){var s='input[type="checkbox"], input[type="'+u+'"]',h=e(),p=function(t){t.each(function(){var t=e(this);h=t.is(s)?h.add(t):h.add(t.find(s))})};if(/^(check|uncheck|toggle|indeterminate|determinate|disable|enable|update|destroy)$/i.test(r))return r=r.toLowerCase(),p(this),h.each(function(){var n=e(this);"destroy"==r?a(n,"ifDestroyed"):t(n,!0,r),e.isFunction(o)&&o()});if("object"!=typeof r&&r)return this;var D=e.extend({checkedClass:c,disabledClass:m,indeterminateClass:f,labelHover:!0},r),w=D.handle,T=D.hoverClass||"hover",x=D.focusClass||"focus",Y=D.activeClass||"active",S=!!D.labelHover,H=D.labelHoverClass||"hover",C=0|(""+D.increaseArea).replace("%","");return"checkbox"!=w&&w!=u||(s='input[type="'+w+'"]'),C<-50&&(C=-50),p(this),h.each(function(){var r=e(this);a(r);var o,s=this,h=s.id,p=-C+"%",f=100+2*C+"%",w={position:"absolute",top:p,left:p,display:"block",width:f,height:f,margin:0,padding:0,background:"#fff",border:0,opacity:0},E=k?{position:"absolute",visibility:"hidden"}:C?w:{position:"absolute",opacity:0},P="checkbox"==s[g]?D.checkboxClass||"icheckbox":D.radioClass||"i"+u,j=e(L+'[for="'+h+'"]').add(r.closest(L)),O=!!D.aria,A=d+"-"+Math.random().toString(36).substr(2,6),I='
")[b]("ifCreated").parent().append(D.insert),o=e('').css(w).appendTo(I),r.data(d,{o:D,s:r.attr("style")}).css(E),!!D.inheritClass&&I[v](s.className||""),!!D.inheritID&&h&&I.attr("id",d+"-"+h),"static"==I.css("position")&&I.css("position","relative"),t(r,!0,_),j.length&&j.on("click.i mouseover.i mouseout.i "+y,function(n){var i=n[g],a=e(this);if(!s[m]){if("click"==i){if(e(n.target).is("a"))return;t(r,!1,!0)}else S&&(/ut|nd/.test(i)?(I[M](T),a[M](H)):(I[v](T),a[v](H)));if(!k)return!1;n.stopPropagation()}}),r.on("click.i focus.i blur.i keyup.i keydown.i keypress.i",function(e){var t=e[g],a=e.keyCode;return"click"!=t&&("keydown"==t&&32==a?(s[g]==u&&s[c]||(s[c]?i(r,c):n(r,c)),!1):void("keyup"==t&&s[g]==u?!s[c]&&n(r,c):/us|ur/.test(t)&&I["blur"==t?M:v](x)))}),o.on("click mousedown mouseup mouseover mouseout "+y,function(e){var n=e[g],i=/wn|up/.test(n)?Y:T;if(!s[m]){if("click"==n?t(r,!1,!0):(/wn|er|in/.test(n)?I[v](i):I[M](i+" "+Y),j.length&&S&&i==T&&j[/ut|nd/.test(n)?M:v](H)),!k)return!1;e.stopPropagation()}})})}}(e||window.Zepto)}).call(t,n("7t+N"))},aM0x:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"১",2:"২",3:"৩",4:"৪",5:"৫",6:"৬",7:"৭",8:"৮",9:"৯",0:"০"},n={"১":"1","২":"2","৩":"3","৪":"4","৫":"5","৬":"6","৭":"7","৮":"8","৯":"9","০":"0"};return e.defineLocale("bn",{months:"জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর".split("_"),monthsShort:"জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে".split("_"),weekdays:"রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার".split("_"),weekdaysShort:"রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি".split("_"),weekdaysMin:"রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি".split("_"),longDateFormat:{LT:"A h:mm সময়",LTS:"A h:mm:ss সময়",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm সময়",LLLL:"dddd, D MMMM YYYY, A h:mm সময়"},calendar:{sameDay:"[আজ] LT",nextDay:"[আগামীকাল] LT",nextWeek:"dddd, LT",lastDay:"[গতকাল] LT",lastWeek:"[গত] dddd, LT",sameElse:"L"},relativeTime:{future:"%s পরে",past:"%s আগে",s:"কয়েক সেকেন্ড",ss:"%d সেকেন্ড",m:"এক মিনিট",mm:"%d মিনিট",h:"এক ঘন্টা",hh:"%d ঘন্টা",d:"এক দিন",dd:"%d দিন",M:"এক মাস",MM:"%d মাস",y:"এক বছর",yy:"%d বছর"},preparse:function(e){return e.replace(/[১২৩৪৫৬৭৮৯০]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/রাত|সকাল|দুপুর|বিকাল|রাত/,meridiemHour:function(e,t){return 12===e&&(e=0),"রাত"===t&&e>=4||"দুপুর"===t&&e<5||"বিকাল"===t?e+12:e},meridiem:function(e,t,n){return e<4?"রাত":e<10?"সকাল":e<17?"দুপুর":e<20?"বিকাল":"রাত"},week:{dow:0,doy:6}})})},aqvp:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n){var i=e+" ";switch(n){case"ss":return i+=1===e?"sekunda":2===e||3===e||4===e?"sekunde":"sekundi";case"m":return t?"jedna minuta":"jedne minute";case"mm":return i+=1===e?"minuta":2===e||3===e||4===e?"minute":"minuta";case"h":return t?"jedan sat":"jednog sata";case"hh":return i+=1===e?"sat":2===e||3===e||4===e?"sata":"sati";case"dd":return i+=1===e?"dan":"dana";case"MM":return i+=1===e?"mjesec":2===e||3===e||4===e?"mjeseca":"mjeseci";case"yy":return i+=1===e?"godina":2===e||3===e||4===e?"godine":"godina"}}return e.defineLocale("bs",{months:"januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sri._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedjelju] [u] LT";case 3:return"[u] [srijedu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[jučer u] LT",lastWeek:function(){switch(this.day()){case 0:case 3:return"[prošlu] dddd [u] LT";case 6:return"[prošle] [subote] [u] LT";case 1:case 2:case 4:case 5:return"[prošli] dddd [u] LT"}},sameElse:"L"},relativeTime:{future:"za %s",past:"prije %s",s:"par sekundi",ss:t,m:t,mm:t,h:t,hh:t,d:"dan",dd:t,M:"mjesec",MM:t,y:"godinu",yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},bXQP:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("fr-ca",{months:"janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre".split("_"),monthsShort:"janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.".split("_"),monthsParseExact:!0,weekdays:"dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi".split("_"),weekdaysShort:"dim._lun._mar._mer._jeu._ven._sam.".split("_"),weekdaysMin:"di_lu_ma_me_je_ve_sa".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY-MM-DD",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Aujourd’hui à] LT",nextDay:"[Demain à] LT",nextWeek:"dddd [à] LT",lastDay:"[Hier à] LT",lastWeek:"dddd [dernier à] LT",sameElse:"L"},relativeTime:{future:"dans %s",past:"il y a %s",s:"quelques secondes",ss:"%d secondes",m:"une minute",mm:"%d minutes",h:"une heure",hh:"%d heures",d:"un jour",dd:"%d jours",M:"un mois",MM:"%d mois",y:"un an",yy:"%d ans"},dayOfMonthOrdinalParse:/\d{1,2}(er|e)/,ordinal:function(e,t){switch(t){default:case"M":case"Q":case"D":case"DDD":case"d":return e+(1===e?"er":"e");case"w":case"W":return e+(1===e?"re":"e")}}})})},c0dd:function(e,t,n){n("chxu"),n("nyna"),n("olwm"),n("GOEF"),n("vg91"),n("AixB"),n("1QPC"),n("gcLw"),n("nRX6"),n("SAIs"),n("EmE7"),n("mN5h")},c1x4:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["секунда","секунде","секунди"],m:["један минут","једне минуте"],mm:["минут","минуте","минута"],h:["један сат","једног сата"],hh:["сат","сата","сати"],dd:["дан","дана","дана"],MM:["месец","месеца","месеци"],yy:["година","године","година"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr-cyrl",{months:"јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар".split("_"),monthsShort:"јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.".split("_"),monthsParseExact:!0,weekdays:"недеља_понедељак_уторак_среда_четвртак_петак_субота".split("_"),weekdaysShort:"нед._пон._уто._сре._чет._пет._суб.".split("_"),weekdaysMin:"не_по_ут_ср_че_пе_су".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[данас у] LT",nextDay:"[сутра у] LT",nextWeek:function(){switch(this.day()){case 0:return"[у] [недељу] [у] LT";case 3:return"[у] [среду] [у] LT";case 6:return"[у] [суботу] [у] LT";case 1:case 2:case 4:case 5:return"[у] dddd [у] LT"}},lastDay:"[јуче у] LT",lastWeek:function(){return["[прошле] [недеље] [у] LT","[прошлог] [понедељка] [у] LT","[прошлог] [уторка] [у] LT","[прошле] [среде] [у] LT","[прошлог] [четвртка] [у] LT","[прошлог] [петка] [у] LT","[прошле] [суботе] [у] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"за %s",past:"пре %s",s:"неколико секунди",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"дан",dd:t.translate,M:"месец",MM:t.translate,y:"годину",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},chxu:function(e,t,n){(function(e){var t=n("7t+N");e.$=e.jQuery=t,n("z1kw"),n("jf49"),n("DBzq"),n("tNVI");var i=n("PJh5");e.moment=i,n("PL/P"),n("WFwX"),n("R6CI"),n("iES9"),n("FDd9"),e.$.AdminLTE={},e.$.AdminLTE.options={},n("Dehm"),n("l77c"),n("a7jZ"),n("3EEf")}).call(t,n("DuR2"))},dURR:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-ma",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:6,doy:12}})})},dyB6:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"e/KL":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("x-pseudo",{months:"J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér".split("_"),monthsShort:"J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc".split("_"),monthsParseExact:!0,weekdays:"S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý".split("_"),weekdaysShort:"S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát".split("_"),weekdaysMin:"S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[T~ódá~ý át] LT",nextDay:"[T~ómó~rró~w át] LT",nextWeek:"dddd [át] LT",lastDay:"[Ý~ést~érdá~ý át] LT",lastWeek:"[L~ást] dddd [át] LT",sameElse:"L"},relativeTime:{future:"í~ñ %s",past:"%s á~gó",s:"á ~féw ~sécó~ñds",ss:"%d s~écóñ~ds",m:"á ~míñ~úté",mm:"%d m~íñú~tés",h:"á~ñ hó~úr",hh:"%d h~óúrs",d:"á ~dáý",dd:"%d d~áýs",M:"á ~móñ~th",MM:"%d m~óñt~hs",y:"á ~ýéár",yy:"%d ý~éárs"},dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},"eBB/":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ko",{months:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),monthsShort:"1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월".split("_"),weekdays:"일요일_월요일_화요일_수요일_목요일_금요일_토요일".split("_"),weekdaysShort:"일_월_화_수_목_금_토".split("_"),weekdaysMin:"일_월_화_수_목_금_토".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"YYYY.MM.DD.",LL:"YYYY년 MMMM D일",LLL:"YYYY년 MMMM D일 A h:mm",LLLL:"YYYY년 MMMM D일 dddd A h:mm",l:"YYYY.MM.DD.",ll:"YYYY년 MMMM D일",lll:"YYYY년 MMMM D일 A h:mm",llll:"YYYY년 MMMM D일 dddd A h:mm"},calendar:{sameDay:"오늘 LT",nextDay:"내일 LT",nextWeek:"dddd LT",lastDay:"어제 LT",lastWeek:"지난주 dddd LT",sameElse:"L"},relativeTime:{future:"%s 후",past:"%s 전",s:"몇 초",ss:"%d초",m:"1분",mm:"%d분",h:"한 시간",hh:"%d시간",d:"하루",dd:"%d일",M:"한 달",MM:"%d달",y:"일 년",yy:"%d년"},dayOfMonthOrdinalParse:/\d{1,2}(일|월|주)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"일";case"M":return e+"월";case"w":case"W":return e+"주";default:return e}},meridiemParse:/오전|오후/,isPM:function(e){return"오후"===e},meridiem:function(e,t,n){return e<12?"오전":"오후"}})})},eHwN:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"-inci",5:"-inci",8:"-inci",70:"-inci",80:"-inci",2:"-nci",7:"-nci",20:"-nci",50:"-nci",3:"-üncü",4:"-üncü",100:"-üncü",6:"-ncı",9:"-uncu",10:"-uncu",30:"-uncu",60:"-ıncı",90:"-ıncı"};return e.defineLocale("az",{months:"yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr".split("_"),monthsShort:"yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek".split("_"),weekdays:"Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə".split("_"),weekdaysShort:"Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən".split("_"),weekdaysMin:"Bz_BE_ÇA_Çə_CA_Cü_Şə".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[bugün saat] LT",nextDay:"[sabah saat] LT",nextWeek:"[gələn həftə] dddd [saat] LT",lastDay:"[dünən] LT",lastWeek:"[keçən həftə] dddd [saat] LT",sameElse:"L"},relativeTime:{future:"%s sonra",past:"%s əvvəl",s:"birneçə saniyə",ss:"%d saniyə",m:"bir dəqiqə",mm:"%d dəqiqə",h:"bir saat",hh:"%d saat",d:"bir gün",dd:"%d gün",M:"bir ay",MM:"%d ay",y:"bir il",yy:"%d il"},meridiemParse:/gecə|səhər|gündüz|axşam/,isPM:function(e){return/^(gündüz|axşam)$/.test(e)},meridiem:function(e,t,n){return e<4?"gecə":e<12?"səhər":e<17?"gündüz":"axşam"},dayOfMonthOrdinalParse:/\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/,ordinal:function(e){if(0===e)return e+"-ıncı";var n=e%10,i=e%100-n,a=e>=100?100:null;return e+(t[n]||t[i]||t[a])},week:{dow:1,doy:7}})})},f4W3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={words:{ss:["sekunda","sekunde","sekundi"],m:["jedan minut","jedne minute"],mm:["minut","minute","minuta"],h:["jedan sat","jednog sata"],hh:["sat","sata","sati"],dd:["dan","dana","dana"],MM:["mesec","meseca","meseci"],yy:["godina","godine","godina"]},correctGrammaticalCase:function(e,t){return 1===e?t[0]:e>=2&&e<=4?t[1]:t[2]},translate:function(e,n,i){var a=t.words[i];return 1===i.length?n?a[0]:a[1]:e+" "+t.correctGrammaticalCase(e,a)}};return e.defineLocale("sr",{months:"januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar".split("_"),monthsShort:"jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.".split("_"),monthsParseExact:!0,weekdays:"nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota".split("_"),weekdaysShort:"ned._pon._uto._sre._čet._pet._sub.".split("_"),weekdaysMin:"ne_po_ut_sr_če_pe_su".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"H:mm",LTS:"H:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY H:mm",LLLL:"dddd, D. MMMM YYYY H:mm"},calendar:{sameDay:"[danas u] LT",nextDay:"[sutra u] LT",nextWeek:function(){switch(this.day()){case 0:return"[u] [nedelju] [u] LT";case 3:return"[u] [sredu] [u] LT";case 6:return"[u] [subotu] [u] LT";case 1:case 2:case 4:case 5:return"[u] dddd [u] LT"}},lastDay:"[juče u] LT",lastWeek:function(){return["[prošle] [nedelje] [u] LT","[prošlog] [ponedeljka] [u] LT","[prošlog] [utorka] [u] LT","[prošle] [srede] [u] LT","[prošlog] [četvrtka] [u] LT","[prošlog] [petka] [u] LT","[prošle] [subote] [u] LT"][this.day()]},sameElse:"L"},relativeTime:{future:"za %s",past:"pre %s",s:"nekoliko sekundi",ss:t.translate,m:t.translate,mm:t.translate,h:t.translate,hh:t.translate,d:"dan",dd:t.translate,M:"mesec",MM:t.translate,y:"godinu",yy:t.translate},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:7}})})},fW1y:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t=["جنوري","فيبروري","مارچ","اپريل","مئي","جون","جولاءِ","آگسٽ","سيپٽمبر","آڪٽوبر","نومبر","ڊسمبر"],n=["آچر","سومر","اڱارو","اربع","خميس","جمع","ڇنڇر"];return e.defineLocale("sd",{months:t,monthsShort:t,weekdays:n,weekdaysShort:n,weekdaysMin:n,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd، D MMMM YYYY HH:mm"},meridiemParse:/صبح|شام/,isPM:function(e){return"شام"===e},meridiem:function(e,t,n){return e<12?"صبح":"شام"},calendar:{sameDay:"[اڄ] LT",nextDay:"[سڀاڻي] LT",nextWeek:"dddd [اڳين هفتي تي] LT",lastDay:"[ڪالهه] LT",lastWeek:"[گزريل هفتي] dddd [تي] LT",sameElse:"L"},relativeTime:{future:"%s پوء",past:"%s اڳ",s:"چند سيڪنڊ",ss:"%d سيڪنڊ",m:"هڪ منٽ",mm:"%d منٽ",h:"هڪ ڪلاڪ",hh:"%d ڪلاڪ",d:"هڪ ڏينهن",dd:"%d ڏينهن",M:"هڪ مهينو",MM:"%d مهينا",y:"هڪ سال",yy:"%d سال"},preparse:function(e){return e.replace(/،/g,",")},postformat:function(e){return e.replace(/,/g,"،")},week:{dow:1,doy:4}})})},g7KF:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t="jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.".split("_"),n="jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_");return e.defineLocale("fy",{months:"jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber".split("_"),monthsShort:function(e,i){return e?/-MMM-/.test(i)?n[e.month()]:t[e.month()]:t},monthsParseExact:!0,weekdays:"snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon".split("_"),weekdaysShort:"si._mo._ti._wo._to._fr._so.".split("_"),weekdaysMin:"Si_Mo_Ti_Wo_To_Fr_So".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[hjoed om] LT",nextDay:"[moarn om] LT",nextWeek:"dddd [om] LT",lastDay:"[juster om] LT",lastWeek:"[ôfrûne] dddd [om] LT",sameElse:"L"},relativeTime:{future:"oer %s",past:"%s lyn",s:"in pear sekonden",ss:"%d sekonden",m:"ien minút",mm:"%d minuten",h:"ien oere",hh:"%d oeren",d:"ien dei",dd:"%d dagen",M:"ien moanne",MM:"%d moannen",y:"ien jier",yy:"%d jierren"},dayOfMonthOrdinalParse:/\d{1,2}(ste|de)/,ordinal:function(e){return e+(1===e||8===e||e>=20?"ste":"de")},week:{dow:1,doy:4}})})},gEQe:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={1:"೧",2:"೨",3:"೩",4:"೪",5:"೫",6:"೬",7:"೭",8:"೮",9:"೯",0:"೦"},n={"೧":"1","೨":"2","೩":"3","೪":"4","೫":"5","೬":"6","೭":"7","೮":"8","೯":"9","೦":"0"};return e.defineLocale("kn",{months:"ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್".split("_"),monthsShort:"ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ".split("_"),monthsParseExact:!0,weekdays:"ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ".split("_"),weekdaysShort:"ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ".split("_"),weekdaysMin:"ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ".split("_"),longDateFormat:{LT:"A h:mm",LTS:"A h:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY, A h:mm",LLLL:"dddd, D MMMM YYYY, A h:mm"},calendar:{sameDay:"[ಇಂದು] LT",nextDay:"[ನಾಳೆ] LT",nextWeek:"dddd, LT",lastDay:"[ನಿನ್ನೆ] LT",lastWeek:"[ಕೊನೆಯ] dddd, LT",sameElse:"L"},relativeTime:{future:"%s ನಂತರ",past:"%s ಹಿಂದೆ",s:"ಕೆಲವು ಕ್ಷಣಗಳು",ss:"%d ಸೆಕೆಂಡುಗಳು",m:"ಒಂದು ನಿಮಿಷ",mm:"%d ನಿಮಿಷ",h:"ಒಂದು ಗಂಟೆ",hh:"%d ಗಂಟೆ",d:"ಒಂದು ದಿನ",dd:"%d ದಿನ",M:"ಒಂದು ತಿಂಗಳು",MM:"%d ತಿಂಗಳು",y:"ಒಂದು ವರ್ಷ",yy:"%d ವರ್ಷ"},preparse:function(e){return e.replace(/[೧೨೩೪೫೬೭೮೯೦]/g,function(e){return n[e]})},postformat:function(e){return e.replace(/\d/g,function(e){return t[e]})},meridiemParse:/ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/,meridiemHour:function(e,t){return 12===e&&(e=0),"ರಾತ್ರಿ"===t?e<4?e:e+12:"ಬೆಳಿಗ್ಗೆ"===t?e:"ಮಧ್ಯಾಹ್ನ"===t?e>=10?e:e+12:"ಸಂಜೆ"===t?e+12:void 0},meridiem:function(e,t,n){return e<4?"ರಾತ್ರಿ":e<10?"ಬೆಳಿಗ್ಗೆ":e<17?"ಮಧ್ಯಾಹ್ನ":e<20?"ಸಂಜೆ":"ರಾತ್ರಿ"},dayOfMonthOrdinalParse:/\d{1,2}(ನೇ)/,ordinal:function(e){return e+"ನೇ"},week:{dow:0,doy:6}})})},gEU3:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("mi",{months:"Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea".split("_"),monthsShort:"Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki".split("_"),monthsRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i,monthsShortStrictRegex:/(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i,weekdays:"Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei".split("_"),weekdaysShort:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),weekdaysMin:"Ta_Ma_Tū_We_Tāi_Pa_Hā".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY [i] HH:mm",LLLL:"dddd, D MMMM YYYY [i] HH:mm"},calendar:{sameDay:"[i teie mahana, i] LT",nextDay:"[apopo i] LT",nextWeek:"dddd [i] LT",lastDay:"[inanahi i] LT",lastWeek:"dddd [whakamutunga i] LT",sameElse:"L"},relativeTime:{future:"i roto i %s",past:"%s i mua",s:"te hēkona ruarua",ss:"%d hēkona",m:"he meneti",mm:"%d meneti",h:"te haora",hh:"%d haora",d:"he ra",dd:"%d ra",M:"he marama",MM:"%d marama",y:"he tau",yy:"%d tau"},dayOfMonthOrdinalParse:/\d{1,2}º/,ordinal:"%dº",week:{dow:1,doy:4}})})},gUgh:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("tet",{months:"Janeiru_Fevereiru_Marsu_Abril_Maiu_Juñu_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru".split("_"),monthsShort:"Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez".split("_"),weekdays:"Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu".split("_"),weekdaysShort:"Dom_Seg_Ters_Kua_Kint_Sest_Sab".split("_"),weekdaysMin:"Do_Seg_Te_Ku_Ki_Ses_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Ohin iha] LT",nextDay:"[Aban iha] LT",nextWeek:"dddd [iha] LT",lastDay:"[Horiseik iha] LT",lastWeek:"dddd [semana kotuk] [iha] LT",sameElse:"L"},relativeTime:{future:"iha %s",past:"%s liuba",s:"minutu balun",ss:"minutu %d",m:"minutu ida",mm:"minutu %d",h:"oras ida",hh:"oras %d",d:"loron ida",dd:"loron %d",M:"fulan ida",MM:"fulan %d",y:"tinan ida",yy:"tinan %d"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},gcLw:function(e,t,n){(function(e,t){if(void 0===e)throw new Error("Kimai requires jQuery");t.kimai={},t(function(){"use strict";t.kimai={init:function(e){void 0!==e&&(t.kimai.settings=t.extend({},t.kimai.defaults,e)),moment.locale(t.kimai.settings.locale),t("a.btn-trash").click(function(e){return confirm(t.kimai.settings.confirmDelete)}),t(".dropdown-toggle").dropdown(),t.kimai.settings.alertSuccessAutoHide>0&&setTimeout(function(){t("div.alert-success").alert("close")},t.kimai.settings.alertSuccessAutoHide),t('input[data-datepicker="on"]').daterangepicker({singleDatePicker:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{format:"YYYY-MM-DD",firstDay:1}}),t('input[data-datepicker="on"]').on("apply.daterangepicker",function(e,n){t(this).val(n.startDate.format("YYYY-MM-DD")),t(this).trigger("change")}),t('input[data-datetimepicker="on"]').daterangepicker({singleDatePicker:!0,timePicker:!0,timePicker24Hour:!0,showDropdowns:!0,autoUpdateInput:!1,locale:{cancelLabel:"Clear",format:"YYYY-MM-DD HH:mm",firstDay:1}}),t('input[data-datetimepicker="on"]').on("apply.daterangepicker",function(e,n){t(this).val(n.startDate.format("YYYY-MM-DD HH:mm")),t(this).trigger("change")}),t("select[data-related-select]").change(function(){var e=t(this).attr("data-api-url").replace("-s-",t(this).val()),n=t(this).attr("data-related-select");t.ajax({url:e,headers:{"X-AUTH-SESSION":!0,"Content-Type":"application/json"},method:"GET",dataType:"json",success:function(e){t("#"+n).find("option").remove().end().find("optgroup").remove().end(),t.each(e,function(e,i){t("#"+n).append('")}),t("#"+n).trigger("change")}})})},pauseRecord:function(e){t(e+" .pull-left i").hover(function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/stop","/pause")),t(this).removeClass("fa-stop-circle").addClass("fa-pause-circle").addClass("text-orange")},function(){var e=t(this).parents("a");e.attr("href",e.attr("href").replace("/pause","/stop")),t(this).removeClass("fa-pause-circle").removeClass("text-orange").addClass("fa-stop-circle")})}},t.kimai.defaults={locale:"en",confirmDelete:"Really delete?",alertSuccessAutoHide:5e3},t.kimai.settings={}})}).call(t,n("7t+N"),n("7t+N"))},hPuz:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},dayOfMonthOrdinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var t=e%10;return e+(1==~~(e%100/10)?"th":1===t?"st":2===t?"nd":3===t?"rd":"th")},week:{dow:1,doy:4}})})},hng5:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("bm",{months:"Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo".split("_"),monthsShort:"Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des".split("_"),weekdays:"Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri".split("_"),weekdaysShort:"Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib".split("_"),weekdaysMin:"Ka_Nt_Ta_Ar_Al_Ju_Si".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"MMMM [tile] D [san] YYYY",LLL:"MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm",LLLL:"dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm"},calendar:{sameDay:"[Bi lɛrɛ] LT",nextDay:"[Sini lɛrɛ] LT",nextWeek:"dddd [don lɛrɛ] LT",lastDay:"[Kunu lɛrɛ] LT",lastWeek:"dddd [tɛmɛnen lɛrɛ] LT",sameElse:"L"},relativeTime:{future:"%s kɔnɔ",past:"a bɛ %s bɔ",s:"sanga dama dama",ss:"sekondi %d",m:"miniti kelen",mm:"miniti %d",h:"lɛrɛ kelen",hh:"lɛrɛ %d",d:"tile kelen",dd:"tile %d",M:"kalo kelen",MM:"kalo %d",y:"san kelen",yy:"san %d"},week:{dow:1,doy:4}})})},iES9:function(e,t){},iNtv:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";function t(e,t,n,i){var a={s:["viensas secunds","'iensas secunds"],ss:[e+" secunds",e+" secunds"],m:["'n míut","'iens míut"],mm:[e+" míuts",e+" míuts"],h:["'n þora","'iensa þora"],hh:[e+" þoras",e+" þoras"],d:["'n ziua","'iensa ziua"],dd:[e+" ziuas",e+" ziuas"],M:["'n mes","'iens mes"],MM:[e+" mesen",e+" mesen"],y:["'n ar","'iens ar"],yy:[e+" ars",e+" ars"]};return i?a[n][0]:t?a[n][0]:a[n][1]}return e.defineLocale("tzl",{months:"Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar".split("_"),monthsShort:"Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec".split("_"),weekdays:"Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi".split("_"),weekdaysShort:"Súl_Lún_Mai_Már_Xhú_Vié_Sát".split("_"),weekdaysMin:"Sú_Lú_Ma_Má_Xh_Vi_Sá".split("_"),longDateFormat:{LT:"HH.mm",LTS:"HH.mm.ss",L:"DD.MM.YYYY",LL:"D. MMMM [dallas] YYYY",LLL:"D. MMMM [dallas] YYYY HH.mm",LLLL:"dddd, [li] D. MMMM [dallas] YYYY HH.mm"},meridiemParse:/d\'o|d\'a/i,isPM:function(e){return"d'o"===e.toLowerCase()},meridiem:function(e,t,n){return e>11?n?"d'o":"D'O":n?"d'a":"D'A"},calendar:{sameDay:"[oxhi à] LT",nextDay:"[demà à] LT",nextWeek:"dddd [à] LT",lastDay:"[ieiri à] LT",lastWeek:"[sür el] dddd [lasteu à] LT",sameElse:"L"},relativeTime:{future:"osprei %s",past:"ja%s",s:t,ss:t,m:t,mm:t,h:t,hh:t,d:t,dd:t,M:t,MM:t,y:t,yy:t},dayOfMonthOrdinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}})})},"j+vx":function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";var t={0:"-ші",1:"-ші",2:"-ші",3:"-ші",4:"-ші",5:"-ші",6:"-шы",7:"-ші",8:"-ші",9:"-шы",10:"-шы",20:"-шы",30:"-шы",40:"-шы",50:"-ші",60:"-шы",70:"-ші",80:"-ші",90:"-шы",100:"-ші"};return e.defineLocale("kk",{months:"қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан".split("_"),monthsShort:"қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел".split("_"),weekdays:"жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі".split("_"),weekdaysShort:"жек_дүй_сей_сәр_бей_жұм_сен".split("_"),weekdaysMin:"жк_дй_сй_ср_бй_жм_сн".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Бүгін сағат] LT",nextDay:"[Ертең сағат] LT",nextWeek:"dddd [сағат] LT",lastDay:"[Кеше сағат] LT",lastWeek:"[Өткен аптаның] dddd [сағат] LT",sameElse:"L"},relativeTime:{future:"%s ішінде",past:"%s бұрын",s:"бірнеше секунд",ss:"%d секунд",m:"бір минут",mm:"%d минут",h:"бір сағат",hh:"%d сағат",d:"бір күн",dd:"%d күн",M:"бір ай",MM:"%d ай",y:"бір жыл",yy:"%d жыл"},dayOfMonthOrdinalParse:/\d{1,2}-(ші|шы)/,ordinal:function(e){var n=e%10,i=e>=100?100:null;return e+(t[e]||t[n]||t[i])},week:{dow:1,doy:7}})})},j8cJ:function(e,t,n){!function(e,t){t(n("PJh5"))}(0,function(e){"use strict";return e.defineLocale("ar-kw",{months:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),monthsShort:"يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر".split("_"),weekdays:"الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت".split("_"),weekdaysShort:"احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت".split("_"),weekdaysMin:"ح_ن_ث_ر_خ_ج_س".split("_"),weekdaysParseExact:!0,longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[اليوم على الساعة] LT",nextDay:"[غدا على الساعة] LT",nextWeek:"dddd [على الساعة] LT",lastDay:"[أمس على الساعة] LT",lastWeek:"dddd [على الساعة] LT",sameElse:"L"},relativeTime:{future:"في %s",past:"منذ %s",s:"ثوان",ss:"%d ثانية",m:"دقيقة",mm:"%d دقائق",h:"ساعة",hh:"%d ساعات",d:"يوم",dd:"%d أيام",M:"شهر",MM:"%d أشهر",y:"سنة",yy:"%d سنوات"},week:{dow:0,doy:12}})})},jf49:function(e,t,n){(function(e){/*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under the MIT license diff --git a/public/build/manifest.json b/public/build/manifest.json index 1fd008c7..cee7c3c9 100644 --- a/public/build/manifest.json +++ b/public/build/manifest.json @@ -1,5 +1,5 @@ { - "build/app.js": "/build/app.js?3c09b71f2907a08b4e32", + "build/app.js": "/build/app.js?07d671a96df83ac52da6", "build/app.css": "/build/app.css?6501689dff217d14b179e3049295343e", "build/fonts/fa-regular-400.woff": "/build/fonts/fa-regular-400.woff?d9e29124", "build/images/blue@2x.png": "/build/images/blue@2x.png?2694acfd", diff --git a/src/API/ActivityController.php b/src/API/ActivityController.php index c9d3a2af..a32370ae 100644 --- a/src/API/ActivityController.php +++ b/src/API/ActivityController.php @@ -13,7 +13,10 @@ namespace App\API; use App\Entity\Activity; use App\Repository\ActivityRepository; +use App\Repository\Query\ActivityQuery; +use FOS\RestBundle\Controller\Annotations as Rest; use FOS\RestBundle\Controller\Annotations\RouteResource; +use FOS\RestBundle\Request\ParamFetcherInterface; use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Nelmio\ApiDocBundle\Annotation as API; @@ -55,12 +58,31 @@ class ActivityController extends Controller * description="Returns the collection of all existing activities", * @SWG\Schema(ref=@API\Model(type=Activity::class)), * ) + * @Rest\QueryParam(name="project", requirements="\d+", strict=true, nullable=true, description="Project ID to filter activities. If none is provided, only global activities will be returned.") + * @Rest\QueryParam(name="visible", requirements="\d+", strict=true, nullable=true, description="Visibility status to filter activities") + * @Rest\QueryParam(name="globals", requirements="true", strict=true, nullable=true, description="Pass true to fetch only global activities") * * @return Response */ - public function cgetAction() + public function cgetAction(ParamFetcherInterface $paramFetcher) { - $data = $this->repository->findAll(); + $query = new ActivityQuery(); + $query->setOrderGlobalsFirst(true) + ->setResultType(ActivityQuery::RESULT_TYPE_OBJECTS); + + if (null !== ($globals = $paramFetcher->get('globals'))) { + $query->setGlobalsOnly(true); + } + + if (null !== ($project = $paramFetcher->get('project'))) { + $query->setProject($project); + } + + if (null !== ($visible = $paramFetcher->get('visible'))) { + $query->setVisibility($visible); + } + + $data = $this->repository->findByQuery($query); $view = new View($data, 200); return $this->viewHandler->handle($view); diff --git a/src/API/ProjectController.php b/src/API/ProjectController.php index 059dbea2..b126ade5 100644 --- a/src/API/ProjectController.php +++ b/src/API/ProjectController.php @@ -13,7 +13,10 @@ namespace App\API; use App\Entity\Project; use App\Repository\ProjectRepository; +use App\Repository\Query\ProjectQuery; +use FOS\RestBundle\Controller\Annotations as Rest; use FOS\RestBundle\Controller\Annotations\RouteResource; +use FOS\RestBundle\Request\ParamFetcherInterface; use FOS\RestBundle\View\View; use FOS\RestBundle\View\ViewHandlerInterface; use Nelmio\ApiDocBundle\Annotation\Model; @@ -55,12 +58,26 @@ class ProjectController extends Controller * description="Returns the collection of all existing projects", * @SWG\Schema(ref=@Model(type=Project::class)), * ) + * @Rest\QueryParam(name="customer", requirements="\d+", strict=true, nullable=true, description="Customer ID to filter projects") + * @Rest\QueryParam(name="visible", requirements="\d+", strict=true, nullable=true, description="Visibility status to filter projects") * + * @param ParamFetcherInterface $paramFetcher * @return Response */ - public function cgetAction() + public function cgetAction(ParamFetcherInterface $paramFetcher) { - $data = $this->repository->findAll(); + $query = new ProjectQuery(); + $query->setResultType(ProjectQuery::RESULT_TYPE_OBJECTS); + + if (null !== ($customer = $paramFetcher->get('customer'))) { + $query->setCustomer($customer); + } + + if (null !== ($visible = $paramFetcher->get('visible'))) { + $query->setVisibility($visible); + } + + $data = $this->repository->findByQuery($query); $view = new View($data, 200); return $this->viewHandler->handle($view); diff --git a/src/Calendar/TimesheetEntity.php b/src/Calendar/TimesheetEntity.php index 71be4586..8c682b4c 100644 --- a/src/Calendar/TimesheetEntity.php +++ b/src/Calendar/TimesheetEntity.php @@ -63,8 +63,8 @@ class TimesheetEntity $this->start = $entry->getBegin(); $this->title = $entry->getActivity()->getName(); $this->description = $entry->getDescription(); - $this->customer = $entry->getActivity()->getProject()->getCustomer()->getName(); - $this->project = $entry->getActivity()->getProject()->getName(); + $this->customer = $entry->getProject()->getCustomer()->getName(); + $this->project = $entry->getProject()->getName(); $this->activity = $entry->getActivity()->getName(); if (null === $entry->getEnd()) { diff --git a/src/Controller/ActivityController.php b/src/Controller/ActivityController.php index 222009b3..effe7697 100644 --- a/src/Controller/ActivityController.php +++ b/src/Controller/ActivityController.php @@ -37,12 +37,11 @@ class ActivityController extends AbstractController public function recentActivitiesAction() { $user = $this->getUser(); - // TODO make days configurable - $activeEntries = $this->getRepository()->getRecentActivities($user, new \DateTime('-30 days')); + $entries = $this->getRepository()->getRecentActivities($user, new \DateTime('-30 days')); return $this->render( 'navbar/recent-activities.html.twig', - ['activities' => $activeEntries] + ['entries' => $entries] ); } } diff --git a/src/Controller/TimesheetController.php b/src/Controller/TimesheetController.php index a5ca72fe..d315dbf0 100644 --- a/src/Controller/TimesheetController.php +++ b/src/Controller/TimesheetController.php @@ -9,7 +9,6 @@ namespace App\Controller; -use App\Entity\Activity; use App\Entity\Timesheet; use App\Form\TimesheetEditForm; use App\Form\Toolbar\TimesheetToolbarForm; @@ -19,6 +18,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Annotation\Route; +use Symfony\Component\Validator\Validator\ValidatorInterface; /** * Controller used to manage timesheets. @@ -143,20 +143,36 @@ class TimesheetController extends AbstractController } /** - * The route to start a running entry. + * The route to re-start a timesheet entry. * * @Route(path="/start/{id}", name="timesheet_start", requirements={"id" = "\d+"}, methods={"GET", "POST"}) - * @Security("is_granted('start', activity)") + * @Security("is_granted('start', timesheet)") * * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response */ - public function startAction(Activity $activity) + public function startAction(ValidatorInterface $validator, Timesheet $timesheet) { $user = $this->getUser(); try { - $this->getRepository()->startRecording($user, $activity); - $this->flashSuccess('timesheet.start.success'); + $entry = new Timesheet(); + $entry + ->setBegin(new \DateTime()) + ->setUser($user) + ->setActivity($timesheet->getActivity()) + ->setProject($timesheet->getProject()) + ; + + $errors = $validator->validate($entry); + + if (count($errors) > 0) { + $this->flashError('timesheet.start.error', ['%reason%' => $errors[0]->getPropertyPath() . ' = ' . $errors[0]->getMessage()]); + } else { + $entityManager = $this->getDoctrine()->getManager(); + $entityManager->persist($entry); + $entityManager->flush(); + $this->flashSuccess('timesheet.start.success'); + } } catch (\Exception $ex) { $this->flashError('timesheet.start.error', ['%reason%' => $ex->getMessage()]); } diff --git a/src/DataFixtures/CustomerFixtures.php b/src/DataFixtures/CustomerFixtures.php index ac58d595..66761254 100644 --- a/src/DataFixtures/CustomerFixtures.php +++ b/src/DataFixtures/CustomerFixtures.php @@ -30,6 +30,8 @@ class CustomerFixtures extends Fixture public const MAX_CUSTOMERS = 15; public const MIN_BUDGET = 0; public const MAX_BUDGET = 100000; + public const MIN_GLOBAL_ACTIVITIES = 5; + public const MAX_GLOBAL_ACTIVITIES = 50; public const MIN_PROJECTS_PER_CUSTOMER = 10; public const MAX_PROJECTS_PER_CUSTOMER = 50; public const MIN_ACTIVITIES_PER_PROJECT = 0; @@ -66,6 +68,16 @@ class CustomerFixtures extends Fixture $manager->flush(); $manager->clear(); } + + $amountGlobalActivities = rand(self::MIN_GLOBAL_ACTIVITIES, self::MAX_GLOBAL_ACTIVITIES); + for ($c = 1; $c <= $amountGlobalActivities; $c++) { + $visibleActivity = 0 != $a % 3; + $activity = $this->createActivity($faker, null, $visibleActivity); + $manager->persist($activity); + } + + $manager->flush(); + $manager->clear(); } /** @@ -113,11 +125,11 @@ class CustomerFixtures extends Fixture /** * @param Generator $faker - * @param Project $project + * @param Project|null $project * @param bool $visible * @return Activity */ - private function createActivity(Generator $faker, Project $project, $visible) + private function createActivity(Generator $faker, ?Project $project, $visible) { $entry = new Activity(); $entry diff --git a/src/DataFixtures/TimesheetFixtures.php b/src/DataFixtures/TimesheetFixtures.php index 45c8afee..089ccde6 100644 --- a/src/DataFixtures/TimesheetFixtures.php +++ b/src/DataFixtures/TimesheetFixtures.php @@ -10,6 +10,7 @@ namespace App\DataFixtures; use App\Entity\Activity; +use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Entity\UserPreference; @@ -56,6 +57,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface { $allUser = $this->getAllUsers($manager); $activities = $this->getAllActivities($manager); + $projects = $this->getAllProjects($manager); $faker = Factory::create(); @@ -80,6 +82,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface $entry = $this->createTimesheetEntry( $user, $activities[array_rand($activities)], + $projects[array_rand($projects)], $description ); @@ -98,6 +101,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface $entry = $this->createTimesheetEntry( $user, $activities[array_rand($activities)], + $projects[array_rand($projects)], null, false ); @@ -126,6 +130,22 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface return $all; } + /** + * @param ObjectManager $manager + * @return Project[] + */ + protected function getAllProjects(ObjectManager $manager) + { + $all = []; + /* @var Project[] $entries */ + $entries = $manager->getRepository(Project::class)->findAll(); + foreach ($entries as $temp) { + $all[$temp->getId()] = $temp; + } + + return $all; + } + /** * @param ObjectManager $manager * @return Activity[] @@ -133,7 +153,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface protected function getAllActivities(ObjectManager $manager) { $all = []; - /* @var User[] $entries */ + /* @var Activity[] $entries */ $entries = $manager->getRepository(Activity::class)->findAll(); foreach ($entries as $temp) { $all[$temp->getId()] = $temp; @@ -142,7 +162,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface return $all; } - private function createTimesheetEntry(User $user, Activity $activity, $description, $setEndDate = true) + private function createTimesheetEntry(User $user, Activity $activity, Project $project, $description, $setEndDate = true) { $start = new \DateTime(); $start = $start->modify('- ' . (rand(1, self::TIMERANGE_DAYS)) . ' days'); @@ -151,6 +171,7 @@ class TimesheetFixtures extends Fixture implements DependentFixtureInterface $entry = new Timesheet(); $entry ->setActivity($activity) + ->setProject($activity->getProject() ?? $project) ->setDescription($description) ->setUser($user) ->setBegin($start); diff --git a/src/Entity/Activity.php b/src/Entity/Activity.php index 274ae08d..21721456 100644 --- a/src/Entity/Activity.php +++ b/src/Entity/Activity.php @@ -34,7 +34,6 @@ class Activity * * @ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="activities") * @ORM\JoinColumn(onDelete="CASCADE") - * @Assert\NotNull() */ private $project; diff --git a/src/Entity/Project.php b/src/Entity/Project.php index 4145df2f..8a7c930c 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -33,7 +33,7 @@ class Project * @var Customer * * @ORM\ManyToOne(targetEntity="App\Entity\Customer", inversedBy="projects") - * @ORM\JoinColumn(onDelete="CASCADE") + * @ORM\JoinColumn(onDelete="CASCADE", nullable=false) * @Assert\NotNull() */ private $customer; @@ -101,6 +101,13 @@ class Project */ private $hourlyRate = null; + /** + * @var Timesheet[] + * + * @ORM\OneToMany(targetEntity="App\Entity\Timesheet", mappedBy="project") + */ + private $timesheets; + /** * @return int */ @@ -118,8 +125,8 @@ class Project } /** - * @param $customer - * @return $this + * @param Customer $customer + * @return Project */ public function setCustomer($customer) { @@ -129,8 +136,6 @@ class Project } /** - * Set name - * * @param string $name * @return Project */ @@ -206,6 +211,25 @@ class Project return $this->budget; } + /** + * @param Timesheet[] $timesheets + * @return Project + */ + public function setTimesheets($timesheets) + { + $this->timesheets = $timesheets; + + return $this; + } + + /** + * @return Timesheet[] + */ + public function getTimesheets() + { + return $this->timesheets; + } + /** * @param Activity[] $activities * @return Project diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php index 57359187..7e3c199c 100644 --- a/src/Entity/Timesheet.php +++ b/src/Entity/Timesheet.php @@ -64,7 +64,7 @@ class Timesheet * @var User * * @ORM\ManyToOne(targetEntity="App\Entity\User") - * @ORM\JoinColumn(name="user", referencedColumnName="id", onDelete="CASCADE") + * @ORM\JoinColumn(name="user", referencedColumnName="id", onDelete="CASCADE", nullable=false) * @Assert\NotNull() */ private $user; @@ -73,11 +73,20 @@ class Timesheet * @var Activity * * @ORM\ManyToOne(targetEntity="App\Entity\Activity", inversedBy="timesheets") - * @ORM\JoinColumn(onDelete="CASCADE") + * @ORM\JoinColumn(onDelete="CASCADE", nullable=false) * @Assert\NotNull() */ private $activity; + /** + * @var Project + * + * @ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="timesheets") + * @ORM\JoinColumn(onDelete="CASCADE", nullable=false) + * @Assert\NotNull() + */ + private $project; + /** * @var string * @@ -232,6 +241,25 @@ class Timesheet return $this->activity; } + /** + * @return Project + */ + public function getProject() + { + return $this->project; + } + + /** + * @param Project $project + * @return Timesheet + */ + public function setProject(Project $project) + { + $this->project = $project; + + return $this; + } + /** * Set description * @@ -317,6 +345,8 @@ class Timesheet } /** + * These validations are used in places, where we don't use a form yet (like the API). + * * @param ExecutionContextInterface $context * @param mixed $payload * @@ -324,6 +354,27 @@ class Timesheet */ public function validate(ExecutionContextInterface $context, $payload) { + if (null === $this->getActivity()) { + $context->buildViolation('A timesheet must have an activity.') + ->atPath('activity') + ->setTranslationDomain('validators') + ->addViolation(); + } + + if (null === $this->getProject()) { + $context->buildViolation('A timesheet must have a project.') + ->atPath('project') + ->setTranslationDomain('validators') + ->addViolation(); + } + + if (null !== $this->getActivity() && null !== $this->getProject() && null !== $this->getActivity()->getProject() && $this->getActivity()->getProject() !== $this->getProject()) { + $context->buildViolation('Project mismatch, project specific activity and timesheet project are different.') + ->atPath('project') + ->setTranslationDomain('validators') + ->addViolation(); + } + if (null !== $this->getEnd() && $this->getEnd()->getTimestamp() < $this->getBegin()->getTimestamp()) { $context->buildViolation('End date must not be earlier then start date.') ->atPath('end') diff --git a/src/Form/ActivityEditForm.php b/src/Form/ActivityEditForm.php index ea4c98ac..42c7886f 100644 --- a/src/Form/ActivityEditForm.php +++ b/src/Form/ActivityEditForm.php @@ -10,8 +10,10 @@ namespace App\Form; use App\Entity\Activity; +use App\Form\Type\CustomerType; use App\Form\Type\ProjectType; use App\Form\Type\YesNoType; +use App\Repository\CustomerRepository; use App\Repository\ProjectRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; @@ -35,8 +37,11 @@ class ActivityEditForm extends AbstractType $entry = $options['data']; $project = null; - if ($entry->getId() !== null) { + $customer = null; + + if (null !== $entry->getProject()) { $project = $entry->getProject(); + $customer = $project->getCustomer(); } $builder @@ -49,9 +54,21 @@ class ActivityEditForm extends AbstractType 'label' => 'label.comment', 'required' => false, ]) - // entity type: project + ->add('customer', CustomerType::class, [ + 'label' => 'label.customer', + 'query_builder' => function (CustomerRepository $repo) use ($customer) { + return $repo->builderForEntityType($customer); + }, + 'required' => false, + 'mapped' => false, + 'attr' => [ + 'data-related-select' => $this->getBlockPrefix() . '_project', + 'data-api-url' => ['get_projects', ['customer' => '-s-']], + ], + ]) ->add('project', ProjectType::class, [ 'label' => 'label.project', + 'required' => false, 'query_builder' => function (ProjectRepository $repo) use ($project) { return $repo->builderForEntityType($project); }, diff --git a/src/Form/TimesheetEditForm.php b/src/Form/TimesheetEditForm.php index 53e2de57..6e3797e0 100644 --- a/src/Form/TimesheetEditForm.php +++ b/src/Form/TimesheetEditForm.php @@ -10,15 +10,21 @@ namespace App\Form; use App\Entity\Timesheet; -use App\Form\Type\ActivityGroupedWithCustomerNameType; +use App\Form\Type\ActivityType; +use App\Form\Type\CustomerType; use App\Form\Type\DurationType; +use App\Form\Type\ProjectType; use App\Form\Type\UserType; use App\Repository\ActivityRepository; +use App\Repository\CustomerRepository; +use App\Repository\ProjectRepository; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\DateTimeType; use Symfony\Component\Form\Extension\Core\Type\NumberType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; use Symfony\Component\OptionsResolver\OptionsResolver; /** @@ -34,9 +40,12 @@ class TimesheetEditForm extends AbstractType /** @var Timesheet $entry */ $entry = $options['data']; - $activity = null; - if ($entry->getId() !== null) { - $activity = $entry->getActivity(); + $activity = $entry->getActivity(); + $project = $entry->getProject(); + $customer = null === $entry->getProject() ? null : $entry->getProject()->getCustomer(); + + if (null === $project && null !== $activity) { + $project = $activity->getProject(); } if (null === $entry->getEnd() || !$options['duration_only']) { @@ -65,7 +74,32 @@ class TimesheetEditForm extends AbstractType } $builder - ->add('activity', ActivityGroupedWithCustomerNameType::class, [ + ->add('customer', CustomerType::class, [ + 'label' => 'label.customer', + 'query_builder' => function (CustomerRepository $repo) use ($customer) { + return $repo->builderForEntityType($customer); + }, + 'data' => $customer ? $customer : '', + 'required' => false, + 'mapped' => false, + 'attr' => [ + 'data-related-select' => $this->getBlockPrefix() . '_project', + 'data-api-url' => ['get_projects', ['customer' => '-s-']], + ], + ]) + ->add('project', ProjectType::class, [ + 'required' => true, + 'placeholder' => '', + 'label' => 'label.project', + 'query_builder' => function (ProjectRepository $repo) use ($project) { + return $repo->builderForEntityType($project); + }, + 'attr' => [ + 'data-related-select' => $this->getBlockPrefix() . '_activity', + 'data-api-url' => ['get_activities', ['project' => '-s-']], + ], + ]) + ->add('activity', ActivityType::class, [ 'label' => 'label.activity', 'query_builder' => function (ActivityRepository $repo) use ($activity) { return $repo->builderForEntityType($activity); @@ -84,6 +118,38 @@ class TimesheetEditForm extends AbstractType 'required' => false, ]) ; + /* + $builder->get('customer')->addEventListener( + FormEvents::POST_SUBMIT, + function (FormEvent $event) { + $customer = $event->getForm()->getData(); + $event->getForm()->getParent()->add('project', ProjectType::class, [ + 'required' => true, + 'placeholder' => '', + 'label' => 'label.project', + 'query_builder' => function (ProjectRepository $repo) use ($customer) { + return $repo->builderForEntityType(null, $customer); + }, + 'attr' => [ + 'data-related-select' => $this->getBlockPrefix() . '_activity', + 'data-api-url' => ['get_activities', ['project' => '-s-']], + ], + ]); + } + ); + */ + $builder->get('project')->addEventListener( + FormEvents::POST_SUBMIT, + function (FormEvent $event) { + $project = $event->getForm()->getData(); + $event->getForm()->getParent()->add('activity', ActivityType::class, [ + 'label' => 'label.activity', + 'query_builder' => function (ActivityRepository $repo) use ($project) { + return $repo->builderForEntityType(null, $project); + }, + ]); + } + ); if ($options['include_user']) { $builder->add('user', UserType::class); diff --git a/src/Form/Toolbar/AbstractToolbarForm.php b/src/Form/Toolbar/AbstractToolbarForm.php index d2eafe06..e83ab9a9 100644 --- a/src/Form/Toolbar/AbstractToolbarForm.php +++ b/src/Form/Toolbar/AbstractToolbarForm.php @@ -150,7 +150,7 @@ abstract class AbstractToolbarForm extends AbstractType 'required' => false, 'query_builder' => function (ProjectRepository $repo) use ($data) { $qb = $repo->builderForEntityType(); - $qb->where('p.customer = :customer')->setParameter('customer', $data['customer']); + $qb->andWhere('p.customer = :customer')->setParameter('customer', $data['customer']); return $qb; }, @@ -176,8 +176,7 @@ abstract class AbstractToolbarForm extends AbstractType 'group_by' => null, 'required' => false, 'query_builder' => function (ActivityRepository $repo) use ($data) { - $qb = $repo->builderForEntityType(); - $qb->where('a.project = :project')->setParameter('project', $data['project']); + $qb = $repo->builderForEntityType(null, $data['project']); return $qb; }, diff --git a/src/Form/Toolbar/TimesheetToolbarForm.php b/src/Form/Toolbar/TimesheetToolbarForm.php index 5af38b55..9c5572e0 100644 --- a/src/Form/Toolbar/TimesheetToolbarForm.php +++ b/src/Form/Toolbar/TimesheetToolbarForm.php @@ -45,6 +45,7 @@ class TimesheetToolbarForm extends AbstractToolbarForm 'entryState.running' => TimesheetQuery::STATE_RUNNING, 'entryState.stopped' => TimesheetQuery::STATE_STOPPED ], + //'attr' => ['class' => 'selectpicker', 'data-live-search' => false, 'data-width' => '100%'] ]); } diff --git a/src/Form/Type/ActivityGroupedWithCustomerNameType.php b/src/Form/Type/ActivityGroupedWithCustomerNameType.php deleted file mode 100644 index cb4ba319..00000000 --- a/src/Form/Type/ActivityGroupedWithCustomerNameType.php +++ /dev/null @@ -1,38 +0,0 @@ -getProject()->getCustomer()->getName(); - } - - /** - * @param Activity $activity - * @return string - */ - public function choiceLabel(Activity $activity) - { - return $activity->getProject()->getName() . ': ' . $activity->getName(); - } -} diff --git a/src/Form/Type/ActivityType.php b/src/Form/Type/ActivityType.php index 1212a80c..68f8d053 100644 --- a/src/Form/Type/ActivityType.php +++ b/src/Form/Type/ActivityType.php @@ -25,7 +25,11 @@ class ActivityType extends AbstractType */ public function groupBy(Activity $activity, $key, $index) { - return '[' . $activity->getProject()->getId() . '] ' . $activity->getProject()->getName(); + if (null === $activity->getProject()) { + return null; + } + + return $activity->getProject()->getName(); } /** @@ -47,8 +51,17 @@ class ActivityType extends AbstractType 'choice_label' => [$this, 'choiceLabel'], 'group_by' => [$this, 'groupBy'], 'query_builder' => function (ActivityRepository $repo) { - return $repo->builderForEntityType(null); + return $repo->builderForEntityType(); }, + 'choice_attr' => function (Activity $activity, $key, $value) { + $attributes = []; + if (null !== $activity->getProject()) { + $attributes['data-project'] = $activity->getProject()->getId(); + } + + return $attributes; + }, + //'attr' => ['class' => 'selectpicker', 'data-size' => 10, 'data-live-search' => true, 'data-width' => '100%'] ]); } diff --git a/src/Form/Type/CustomerType.php b/src/Form/Type/CustomerType.php index 845e681b..6937e850 100644 --- a/src/Form/Type/CustomerType.php +++ b/src/Form/Type/CustomerType.php @@ -32,6 +32,7 @@ class CustomerType extends AbstractType 'query_builder' => function (CustomerRepository $repo) { return $repo->builderForEntityType(null); }, + //'attr' => ['class' => 'selectpicker', 'data-size' => 10, 'data-live-search' => true, 'data-width' => '100%'] ]); } diff --git a/src/Form/Type/PageSizeType.php b/src/Form/Type/PageSizeType.php index b84e0e83..5bc95bf8 100644 --- a/src/Form/Type/PageSizeType.php +++ b/src/Form/Type/PageSizeType.php @@ -26,6 +26,7 @@ class PageSizeType extends AbstractType $resolver->setDefaults([ 'label' => 'label.pageSize', 'choices' => [10 => 10, 25 => 25, 50 => 50, 75 => 75, 100 => 100], + //'attr' => ['class' => 'selectpicker', 'data-live-search' => false, 'data-width' => '100%'] ]); } diff --git a/src/Form/Type/ProjectType.php b/src/Form/Type/ProjectType.php index c70c07e3..66e35ecd 100644 --- a/src/Form/Type/ProjectType.php +++ b/src/Form/Type/ProjectType.php @@ -35,6 +35,7 @@ class ProjectType extends AbstractType 'query_builder' => function (ProjectRepository $repo) { return $repo->builderForEntityType(null); }, + //'attr' => ['class' => 'selectpicker', 'data-size' => 10, 'data-live-search' => true, 'data-width' => '100%'] ]); } diff --git a/src/Form/Type/VisibilityType.php b/src/Form/Type/VisibilityType.php index d431fd2b..749d3f06 100644 --- a/src/Form/Type/VisibilityType.php +++ b/src/Form/Type/VisibilityType.php @@ -31,6 +31,7 @@ class VisibilityType extends AbstractType 'yes' => VisibilityQuery::SHOW_VISIBLE, 'no' => VisibilityQuery::SHOW_HIDDEN, ], + //'attr' => ['class' => 'selectpicker', 'data-live-search' => false, 'data-width' => '100%'] ]); } diff --git a/src/Migrations/Version20181031220003.php b/src/Migrations/Version20181031220003.php new file mode 100644 index 00000000..a1d368ae --- /dev/null +++ b/src/Migrations/Version20181031220003.php @@ -0,0 +1,127 @@ +getPlatform(); + + if (!in_array($platform, ['sqlite', 'mysql'])) { + $this->abortIf(true, 'Unsupported database platform: ' . $platform); + } + + $timesheet = $this->getTableName('timesheet'); + $projects = $this->getTableName('projects'); + $activities = $this->getTableName('activities'); + $users = $this->getTableName('users'); + $customers = $this->getTableName('customers'); + + if ($platform === 'sqlite') { + // project table + $this->addSql('DROP INDEX IDX_407F12069395C3F3'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $projects . ' AS SELECT id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate FROM ' . $projects); + $this->addSql('DROP TABLE ' . $projects); + $this->addSql('CREATE TABLE ' . $projects . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, customer_id INTEGER NOT NULL, name VARCHAR(255) NOT NULL COLLATE BINARY, order_number CLOB DEFAULT NULL COLLATE BINARY, comment CLOB DEFAULT NULL COLLATE BINARY, visible BOOLEAN NOT NULL, budget NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL, CONSTRAINT FK_407F12069395C3F3 FOREIGN KEY (customer_id) REFERENCES ' . $customers . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $projects . ' (id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate) SELECT id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate FROM __temp__' . $projects); + $this->addSql('DROP TABLE __temp__' . $projects); + $this->addSql('CREATE INDEX IDX_407F12069395C3F3 ON ' . $projects . ' (customer_id)'); + // timesheet table + $this->addSql('DROP INDEX IDX_4F60C6B18D93D649'); + $this->addSql('DROP INDEX IDX_4F60C6B181C06096'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM ' . $timesheet); + $this->addSql('DROP TABLE ' . $timesheet); + $this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, project_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL COLLATE BINARY, rate NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL, CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $users . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $activities . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B1166D1F9C FOREIGN KEY (project_id) REFERENCES ' . $projects . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM __temp__' . $timesheet); + $this->addSql('DROP TABLE __temp__' . $timesheet); + $this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)'); + $this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)'); + $this->addSql('CREATE INDEX IDX_4F60C6B1166D1F9C ON ' . $timesheet . ' (project_id)'); + } else { + // project table + $this->addSql('ALTER TABLE ' . $projects . ' CHANGE customer_id customer_id INT NOT NULL'); + // timesheet table + $this->addSql('ALTER TABLE ' . $timesheet . ' ADD project_id INT DEFAULT NULL AFTER activity_id'); + $this->addSql('ALTER TABLE ' . $timesheet . ' ADD CONSTRAINT FK_4F60C6B1166D1F9C FOREIGN KEY (project_id) REFERENCES ' . $projects . ' (id) ON DELETE CASCADE'); + $this->addSql('CREATE INDEX IDX_4F60C6B1166D1F9C ON ' . $timesheet . ' (project_id)'); + } + + // update timesheet table and insert project_id from activity table + $this->addSql('UPDATE ' . $timesheet . ' SET project_id = (SELECT project_id FROM ' . $activities . ' WHERE id = activity_id)'); + + // now update the timesheet table and disallow null values for all required columns (that was a bug before) + if ($platform === 'sqlite') { + $this->addSql('DROP INDEX IDX_4F60C6B181C06096'); + $this->addSql('DROP INDEX IDX_4F60C6B18D93D649'); + $this->addSql('DROP INDEX IDX_4F60C6B1166D1F9C'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM ' . $timesheet); + $this->addSql('DROP TABLE ' . $timesheet); + $this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user INTEGER NOT NULL, activity_id INTEGER NOT NULL, project_id INTEGER NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL COLLATE BINARY, rate NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL, CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $users . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $activities . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B1166D1F9C FOREIGN KEY (project_id) REFERENCES ' . $projects . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)'); + $this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate) SELECT id, user, activity_id, project_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM __temp__' . $timesheet); + $this->addSql('DROP TABLE __temp__' . $timesheet); + $this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)'); + $this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)'); + $this->addSql('CREATE INDEX IDX_4F60C6B1166D1F9C ON ' . $timesheet . ' (project_id)'); + } else { + $this->addSql('ALTER TABLE ' . $timesheet . ' CHANGE project_id project_id INT NOT NULL, CHANGE user user INT NOT NULL, CHANGE activity_id activity_id INT NOT NULL'); + } + } + + public function down(Schema $schema): void + { + $platform = $this->getPlatform(); + + if (!in_array($platform, ['sqlite', 'mysql'])) { + $this->abortIf(true, 'Unsupported database platform: ' . $platform); + } + + $timesheet = $this->getTableName('timesheet'); + $projects = $this->getTableName('projects'); + + if ($platform === 'sqlite') { + // project table + $this->addSql('DROP INDEX IDX_407F12069395C3F3'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $projects . ' AS SELECT id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate FROM ' . $projects); + $this->addSql('DROP TABLE ' . $projects); + $this->addSql('CREATE TABLE ' . $projects . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, name VARCHAR(255) NOT NULL, order_number CLOB DEFAULT NULL, comment CLOB DEFAULT NULL, visible BOOLEAN NOT NULL, budget NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL, customer_id INTEGER DEFAULT NULL)'); + $this->addSql('INSERT INTO ' . $projects . ' (id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate) SELECT id, customer_id, name, order_number, comment, visible, budget, fixed_rate, hourly_rate FROM __temp__' . $projects); + $this->addSql('DROP TABLE __temp__' . $projects); + $this->addSql('CREATE INDEX IDX_407F12069395C3F3 ON ' . $projects . ' (customer_id)'); + // timesheet table + $this->addSql('DROP INDEX IDX_4F60C6B1166D1F9C'); + $this->addSql('DROP INDEX IDX_4F60C6B18D93D649'); + $this->addSql('DROP INDEX IDX_4F60C6B181C06096'); + $this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM ' . $timesheet); + $this->addSql('DROP TABLE ' . $timesheet); + $this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, fixed_rate NUMERIC(10, 2) DEFAULT NULL, hourly_rate NUMERIC(10, 2) DEFAULT NULL)'); + $this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate, fixed_rate, hourly_rate FROM __temp__' . $timesheet); + $this->addSql('DROP TABLE __temp__' . $timesheet); + $this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)'); + $this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)'); + } else { + // project table + $this->addSql('ALTER TABLE ' . $projects . ' CHANGE customer_id customer_id INT DEFAULT NULL'); + // timesheet table + $this->addSql('ALTER TABLE ' . $timesheet . ' DROP FOREIGN KEY FK_4F60C6B1166D1F9C'); + $this->addSql('DROP INDEX IDX_4F60C6B1166D1F9C ON ' . $timesheet); + $this->addSql('ALTER TABLE ' . $timesheet . ' DROP project_id, CHANGE user user INT DEFAULT NULL, CHANGE activity_id activity_id INT DEFAULT NULL'); + } + } +} diff --git a/src/Repository/ActivityRepository.php b/src/Repository/ActivityRepository.php index 236d32e5..684007b5 100644 --- a/src/Repository/ActivityRepository.php +++ b/src/Repository/ActivityRepository.php @@ -10,6 +10,7 @@ namespace App\Repository; use App\Entity\Activity; +use App\Entity\Project; use App\Entity\Timesheet; use App\Entity\User; use App\Model\ActivityStatistic; @@ -35,7 +36,7 @@ class ActivityRepository extends AbstractRepository /** * @param User|null $user * @param \DateTime|null $startFrom - * @return mixed + * @return Timesheet[] */ public function getRecentActivities(User $user = null, \DateTime $startFrom = null) { @@ -45,7 +46,7 @@ class ActivityRepository extends AbstractRepository ->distinct() ->from(Timesheet::class, 't') ->join('t.activity', 'a') - ->join('a.project', 'p') + ->join('t.project', 'p') ->join('p.customer', 'c') ->andWhere($qb->expr()->isNotNull('t.end')) ->andWhere('a.visible = 1') @@ -66,15 +67,7 @@ class ActivityRepository extends AbstractRepository ->setParameter('begin', $startFrom); } - $results = $qb->getQuery()->getResult(); - - $activities = []; - /* @var Timesheet $entry */ - foreach ($results as $entry) { - $activities[] = $entry->getActivity(); - } - - return $activities; + return $qb->getQuery()->getResult(); } /** @@ -118,14 +111,21 @@ class ActivityRepository extends AbstractRepository /** * Returns a query builder that is used for ActivityType and your own 'query_builder' option. * - * @param Activity|null $entity + * @param Activity|string|null $activity + * @param Project|string|null $project * @return \Doctrine\ORM\QueryBuilder */ - public function builderForEntityType(Activity $entity = null) + public function builderForEntityType($activity = null, $project = null) { $query = new ActivityQuery(); - $query->setHiddenEntity($entity); + $query->setHiddenEntity($activity); $query->setResultType(ActivityQuery::RESULT_TYPE_QUERYBUILDER); + $query->setProject($project); + $query->setOrderGlobalsFirst(true); + + if (null === $activity && $project === null) { + $query->setGlobalsOnly(true); + } return $this->findByQuery($query); } @@ -140,34 +140,68 @@ class ActivityRepository extends AbstractRepository $qb->select('a', 'p', 'c') ->from(Activity::class, 'a') - ->join('a.project', 'p') - ->join('p.customer', 'c') + ->leftJoin('a.project', 'p') + ->leftJoin('p.customer', 'c') ->orderBy('a.' . $query->getOrderBy(), $query->getOrder()); + $where = $qb->expr()->andX(); + if (ActivityQuery::SHOW_VISIBLE == $query->getVisibility()) { + $where->add('a.visible = :visible'); if (!$query->isExclusiveVisibility()) { - $qb->andWhere('c.visible = 1'); - $qb->andWhere('p.visible = 1'); - } - $qb->andWhere('a.visible = 1'); - - /** @var Activity $entity */ - $entity = $query->getHiddenEntity(); - if (null !== $entity) { - $qb->orWhere('a.id = :activity')->setParameter('activity', $entity); + $where->add( + $qb->expr()->orX( + $qb->expr()->eq('c.visible', ':visible'), + $qb->expr()->isNull('c.visible') + ) + ); + $where->add( + $qb->expr()->orX( + $qb->expr()->eq('p.visible', ':visible'), + $qb->expr()->isNull('p.visible') + ) + ); } + $qb->setParameter('visible', 1); } elseif (ActivityQuery::SHOW_HIDDEN == $query->getVisibility()) { - $qb->andWhere('a.visible = 0'); + $where->add('a.visible = :visible'); + $qb->setParameter('visible', 0); } - if (null !== $query->getProject()) { - $qb->andWhere('a.project = :project') - ->setParameter('project', $query->getProject()); + if ($query->isGlobalsOnly()) { + $where->add($qb->expr()->isNull('a.project')); + } elseif (null !== $query->getProject()) { + $where->add( + $qb->expr()->orX( + $qb->expr()->eq('a.project', ':project'), + $qb->expr()->isNull('a.project') + ) + ); + $qb->setParameter('project', $query->getProject()); } elseif (null !== $query->getCustomer()) { - $qb->andWhere('p.customer = :customer') - ->setParameter('customer', $query->getCustomer()); + $where->add('p.customer = :customer'); + $qb->setParameter('customer', $query->getCustomer()); } + $or = $qb->expr()->orX(); + + // this must always be the last part before the or + $or->add($where); + + // this must always be the last part of the query + /** @var Activity $entity */ + $entity = $query->getHiddenEntity(); + if (null !== $entity) { + $or->add($qb->expr()->eq('a.id', ':activity')); + $qb->setParameter('activity', $entity); + } + + if ($query->isOrderGlobalsFirst()) { + $qb->orderBy('a.project', 'ASC'); + } + + $qb->andWhere($or); + return $this->getBaseQueryResult($qb, $query); } } diff --git a/src/Repository/CustomerRepository.php b/src/Repository/CustomerRepository.php index c9112fc3..edff162c 100644 --- a/src/Repository/CustomerRepository.php +++ b/src/Repository/CustomerRepository.php @@ -60,7 +60,7 @@ class CustomerRepository extends AbstractRepository ->join(Project::class, 'p') ->join(Customer::class, 'c') ->andWhere('t.activity = a.id') - ->andWhere('a.project = p.id') + ->andWhere('t.project = p.id') ->andWhere('p.customer = c.id') ->andWhere('c.id = :customer') ; diff --git a/src/Repository/ProjectRepository.php b/src/Repository/ProjectRepository.php index c28c4fa7..96a8184f 100644 --- a/src/Repository/ProjectRepository.php +++ b/src/Repository/ProjectRepository.php @@ -10,6 +10,7 @@ namespace App\Repository; use App\Entity\Activity; +use App\Entity\Customer; use App\Entity\Project; use App\Entity\Timesheet; use App\Model\ProjectStatistic; @@ -41,7 +42,7 @@ class ProjectRepository extends AbstractRepository } /** - * Retrieves statistics for one activity. + * Retrieves statistics for one project. * * @param Project $project * @return ProjectStatistic @@ -55,7 +56,7 @@ class ProjectRepository extends AbstractRepository ->addSelect('COUNT(DISTINCT(a.id)) as activityAmount') ->from(Activity::class, 'a') ->join(Timesheet::class, 't') - ->where('a.project = :project') + ->where('t.project = :project') ->andWhere('t.activity = a.id') ; @@ -79,12 +80,14 @@ class ProjectRepository extends AbstractRepository * Returns a query builder that is used for ProjectType and your own 'query_builder' option. * * @param Project|null $entity - * @return \Doctrine\ORM\QueryBuilder + * @param Customer|null $customer + * @return array|QueryBuilder|Pagerfanta */ - public function builderForEntityType(Project $entity = null) + public function builderForEntityType(Project $entity = null, Customer $customer = null) { $query = new ProjectQuery(); $query->setHiddenEntity($entity); + $query->setCustomer($customer); $query->setResultType(ProjectQuery::RESULT_TYPE_QUERYBUILDER); return $this->findByQuery($query); diff --git a/src/Repository/Query/ActivityQuery.php b/src/Repository/Query/ActivityQuery.php index 68cd357b..59f7d23b 100644 --- a/src/Repository/Query/ActivityQuery.php +++ b/src/Repository/Query/ActivityQuery.php @@ -17,12 +17,58 @@ use App\Entity\Project; class ActivityQuery extends ProjectQuery { /** - * @var Project + * @var Project|int */ protected $project; + /** + * @var bool + */ + protected $orderGlobalsFirst = false; + /** + * @var bool + */ + protected $globalsOnly = false; /** - * @return Project + * @return bool + */ + public function isOrderGlobalsFirst(): bool + { + return $this->orderGlobalsFirst; + } + + /** + * @param bool $orderGlobalsFirst + * @return ActivityQuery + */ + public function setOrderGlobalsFirst(bool $orderGlobalsFirst) + { + $this->orderGlobalsFirst = $orderGlobalsFirst; + + return $this; + } + + /** + * @return bool + */ + public function isGlobalsOnly(): bool + { + return $this->globalsOnly; + } + + /** + * @param bool $globalsOnly + * @return ActivityQuery + */ + public function setGlobalsOnly(bool $globalsOnly) + { + $this->globalsOnly = $globalsOnly; + + return $this; + } + + /** + * @return Project|int */ public function getProject() { @@ -30,10 +76,10 @@ class ActivityQuery extends ProjectQuery } /** - * @param Project $project + * @param Project|int $project * @return $this */ - public function setProject(Project $project = null) + public function setProject($project = null) { $this->project = $project; diff --git a/src/Repository/Query/BaseQuery.php b/src/Repository/Query/BaseQuery.php index d6a81c18..26c23c10 100644 --- a/src/Repository/Query/BaseQuery.php +++ b/src/Repository/Query/BaseQuery.php @@ -140,7 +140,7 @@ class BaseQuery } /** - * @param string $resultType + * @param $resultType * @return $this * @throws \InvalidArgumentException */ @@ -166,7 +166,7 @@ class BaseQuery } /** - * @param object $hiddenEntity + * @param object|string $hiddenEntity * @return BaseQuery */ public function setHiddenEntity($hiddenEntity) diff --git a/src/Repository/Query/ProjectQuery.php b/src/Repository/Query/ProjectQuery.php index b91e6d08..9f00b1c0 100644 --- a/src/Repository/Query/ProjectQuery.php +++ b/src/Repository/Query/ProjectQuery.php @@ -17,12 +17,12 @@ use App\Entity\Customer; class ProjectQuery extends VisibilityQuery { /** - * @var Customer + * @var Customer|int */ protected $customer; /** - * @return Customer + * @return Customer|int */ public function getCustomer() { @@ -30,10 +30,10 @@ class ProjectQuery extends VisibilityQuery } /** - * @param Customer $customer + * @param Customer|int $customer * @return $this */ - public function setCustomer(Customer $customer = null) + public function setCustomer($customer = null) { $this->customer = $customer; diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index 413492f7..6e8efa62 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -9,7 +9,6 @@ namespace App\Repository; -use App\Entity\Activity; use App\Entity\Timesheet; use App\Entity\User; use App\Model\Statistic\Month; @@ -54,28 +53,6 @@ class TimesheetRepository extends AbstractRepository return true; } - /** - * @param User $user - * @param Activity $activity - * @return Timesheet - * @throws \Doctrine\ORM\ORMException - * @throws \Doctrine\ORM\OptimisticLockException - */ - public function startRecording(User $user, Activity $activity) - { - $entry = new Timesheet(); - $entry - ->setBegin(new DateTime()) - ->setUser($user) - ->setActivity($activity); - - $entityManager = $this->getEntityManager(); - $entityManager->persist($entry); - $entityManager->flush(); - - return $entry; - } - /** * @param string $type * @param DateTime|null $begin @@ -223,17 +200,17 @@ class TimesheetRepository extends AbstractRepository $qb->andWhere($qb->expr()->isNotNull('t.end')); } + if (null !== $user) { + $qb->andWhere('t.user = :user') + ->setParameter('user', $user); + } + $qb ->orderBy('year', 'DESC') ->addOrderBy('month', 'ASC') ->groupBy('year') ->addGroupBy('month'); - if (null !== $user) { - $qb->andWhere('t.user = :user') - ->setParameter('user', $user); - } - $years = []; foreach ($qb->getQuery()->execute() as $statRow) { $curYear = $statRow['year']; @@ -267,7 +244,7 @@ class TimesheetRepository extends AbstractRepository $qb->select('t', 'a', 'p', 'c') ->from(Timesheet::class, 't') ->join('t.activity', 'a') - ->join('a.project', 'p') + ->join('t.project', 'p') ->join('p.customer', 'c') ->where($qb->expr()->gt('t.begin', '0')) ->andWhere($qb->expr()->isNull('t.end')) @@ -293,10 +270,10 @@ class TimesheetRepository extends AbstractRepository $qb->select('t', 'a', 'p', 'c', 'u') ->from(Timesheet::class, 't') - ->join('t.activity', 'a') - ->join('t.user', 'u') - ->join('a.project', 'p') - ->join('p.customer', 'c') + ->leftJoin('t.activity', 'a') + ->leftJoin('t.user', 'u') + ->leftJoin('t.project', 'p') + ->leftJoin('p.customer', 'c') ->orderBy('t.' . $query->getOrderBy(), $query->getOrder()); if (null !== $query->getUser()) { @@ -323,7 +300,7 @@ class TimesheetRepository extends AbstractRepository $qb->andWhere('t.activity = :activity') ->setParameter('activity', $query->getActivity()); } elseif (null !== $query->getProject()) { - $qb->andWhere('a.project = :project') + $qb->andWhere('t.project = :project') ->setParameter('project', $query->getProject()); } elseif (null !== $query->getCustomer()) { $qb->andWhere('p.customer = :customer') diff --git a/src/Timesheet/Calculator/RateCalculator.php b/src/Timesheet/Calculator/RateCalculator.php index 7fc4a305..9be7409b 100644 --- a/src/Timesheet/Calculator/RateCalculator.php +++ b/src/Timesheet/Calculator/RateCalculator.php @@ -74,7 +74,7 @@ class RateCalculator implements CalculatorInterface return $activity->getHourlyRate(); } - $project = $activity->getProject(); + $project = $record->getProject(); if (null !== $project) { if (null !== $project->getHourlyRate()) { return $project->getHourlyRate(); @@ -104,7 +104,7 @@ class RateCalculator implements CalculatorInterface return $activity->getFixedRate(); } - $project = $activity->getProject(); + $project = $record->getProject(); if (null !== $project) { if (null !== $project->getFixedRate()) { return $project->getFixedRate(); diff --git a/src/Voter/TimesheetVoter.php b/src/Voter/TimesheetVoter.php index a9b64ced..efc6c6e8 100644 --- a/src/Voter/TimesheetVoter.php +++ b/src/Voter/TimesheetVoter.php @@ -44,10 +44,6 @@ class TimesheetVoter extends AbstractVoter return false; } - if ($subject instanceof Activity && self::START == $attribute) { - return true; - } - if (!$subject instanceof Timesheet) { return false; } @@ -71,38 +67,18 @@ class TimesheetVoter extends AbstractVoter switch ($attribute) { case self::STOP: - if (!$subject instanceof Timesheet) { - return false; - } - return $this->canStop($subject, $user, $token); case self::START: - if (!$subject instanceof Activity) { - return false; - } - return $this->canStart($subject, $user, $token); case self::VIEW: - if (!$subject instanceof Timesheet) { - return false; - } - return $this->canView($subject, $user, $token); case self::EDIT: - if (!$subject instanceof Timesheet) { - return false; - } - return $this->canEdit($subject, $user, $token); case self::DELETE: - if (!$subject instanceof Timesheet) { - return false; - } - return $this->canDelete($subject, $user, $token); } @@ -122,23 +98,21 @@ class TimesheetVoter extends AbstractVoter } /** - * @param Activity $activity + * @param Timesheet $timesheet * @param User $user * @param TokenInterface $token * @return bool */ - protected function canStart(Activity $activity, User $user, TokenInterface $token) + protected function canStart(Timesheet $timesheet, User $user, TokenInterface $token) { // we could check the amount of active entries // if a teamlead starts an entry for another user, check that this user is part of his team - if (!$activity->getVisible()) { + if (!$timesheet->getActivity()->getVisible() || !$timesheet->getProject()->getVisible()) { return false; } - if (!$activity->getProject()->getVisible()) { - return false; - } - if (!$activity->getProject()->getCustomer()->getVisible()) { + + if (!$timesheet->getProject()->getCustomer()->getVisible()) { return false; } diff --git a/templates/admin/activity.html.twig b/templates/admin/activity.html.twig index 4c045163..111a40af 100644 --- a/templates/admin/activity.html.twig +++ b/templates/admin/activity.html.twig @@ -33,10 +33,18 @@ {{ entry.name }} - {{ widgets.label_customer(entry.project.customer) }} + {% if entry.project and entry.project.customer %} + {{ widgets.label_customer(entry.project.customer) }} + {% else %} + {#{ widgets.label('x', 'success') }#} + {% endif %} - {{ widgets.label_project(entry.project) }} + {% if entry.project %} + {{ widgets.label_project(entry.project) }} + {% else %} + {#{ widgets.label('x', 'success') }#} + {% endif %} {{ entry.comment }} {{ widgets.label_visible(entry.visible) }} diff --git a/templates/admin/activity_delete.html.twig b/templates/admin/activity_delete.html.twig index b323e9fe..aa84d088 100644 --- a/templates/admin/activity_delete.html.twig +++ b/templates/admin/activity_delete.html.twig @@ -9,12 +9,18 @@ {% set params = { '%activity%': '' ~ activity.name ~ '', - '%project%': '' ~ activity.project.name ~ '', - '%customer%': '' ~ activity.project.customer.name ~ '', + '%project%': '-', + '%customer%': '-', '%records%': '' ~ stats.recordAmount ~ '', '%duration%': '' ~ stats.recordDuration|duration ~ '' } %} + {% if activity.project is not null %} + {% set params = params|merge({ + '%project%': '' ~ activity.project.name ~ '', + '%customer%': '' ~ activity.project.customer.name ~ '', + }) %} + {% endif %} {{ include('default/_form_delete.html.twig', { 'message': "admin_activity.delete_confirm"|trans(params)|raw, 'form': form, diff --git a/templates/admin/timesheet.html.twig b/templates/admin/timesheet.html.twig index 492099e1..ac707387 100644 --- a/templates/admin/timesheet.html.twig +++ b/templates/admin/timesheet.html.twig @@ -48,7 +48,7 @@ {{ entry.end|date("H:i") }} {% endif %} {{ entry.duration|duration }} - {{ entry.rate|money(entry.activity.project.customer.currency) }} + {{ entry.rate|money(entry.project.customer.currency) }} {% else %} {% if not duration_only %} ‐ diff --git a/templates/bundles/NelmioApiDocBundle/SwaggerUI/index.html.twig b/templates/bundles/NelmioApiDocBundle/SwaggerUI/index.html.twig new file mode 100644 index 00000000..37aeb53e --- /dev/null +++ b/templates/bundles/NelmioApiDocBundle/SwaggerUI/index.html.twig @@ -0,0 +1,18 @@ + + + + + {{ swagger_data.spec.info.title }} + + + + {# json_encode(65) is for JSON_UNESCAPED_SLASHES|JSON_HEX_TAG to avoid JS XSS #} + + + +
+ + + + + diff --git a/templates/form/kimai-theme.html.twig b/templates/form/kimai-theme.html.twig index c245c59d..355e9bf0 100644 --- a/templates/form/kimai-theme.html.twig +++ b/templates/form/kimai-theme.html.twig @@ -6,3 +6,10 @@ {% endif %} {{ parent() }} {% endblock form_label %} + +{% block choice_widget_collapsed %} + {% if 'data-api-url' in attr|keys %} + {% set attr = attr|merge({'data-api-url': path(attr['data-api-url']|first, attr['data-api-url']|last)}) %} + {% endif %} + {{ parent() }} +{% endblock %} diff --git a/templates/invoice/preview.html.twig b/templates/invoice/preview.html.twig index b72736b1..fabb7dc4 100644 --- a/templates/invoice/preview.html.twig +++ b/templates/invoice/preview.html.twig @@ -33,7 +33,7 @@ {% if entry.description is not empty %} {{ entry.description|desc2html }} {% else %} - {{ entry.activity.name }} / {{ entry.activity.project.name }} + {{ entry.activity.name }} / {{ entry.project.name }} {% endif %} diff --git a/templates/invoice/renderer/default.html.twig b/templates/invoice/renderer/default.html.twig index ae6b8ba5..c857dee5 100644 --- a/templates/invoice/renderer/default.html.twig +++ b/templates/invoice/renderer/default.html.twig @@ -66,7 +66,7 @@ {% for entry in model.calculator.entries %} {{ entry.begin|date_short }} - {{ entry.activity.name }} / {{ entry.activity.project.name }} + {{ entry.activity.name }} / {{ entry.project.name }} {{ app.user.getPreferenceValue('hourly_rate')|money(model.calculator.currency) }} {{ entry.duration|duration }} {{ entry.rate|money(model.calculator.currency) }} diff --git a/templates/invoice/renderer/timesheet.html.twig b/templates/invoice/renderer/timesheet.html.twig index b462990c..531f85e4 100644 --- a/templates/invoice/renderer/timesheet.html.twig +++ b/templates/invoice/renderer/timesheet.html.twig @@ -72,7 +72,7 @@ {% if model.query.user is empty %} {{ widgets.username(entry.user) }} {% endif %} - {{ entry.activity.name }} / {{ entry.activity.project.name }} + {{ entry.activity.name }} / {{ entry.project.name }} {{ entry.duration|duration }} {% endfor %} diff --git a/templates/macros/widgets.html.twig b/templates/macros/widgets.html.twig index 809c9419..0bdbac8e 100644 --- a/templates/macros/widgets.html.twig +++ b/templates/macros/widgets.html.twig @@ -56,11 +56,18 @@ {% macro label_activity(activity) %} {% import _self as macro %} - {% if activity.visible and activity.project.visible and activity.project.customer.visible %} - {{ macro.label(activity.name, 'primary', activity.project.customer.name ~ ': ' ~ activity.project.name) }} - {% else %} - {{ macro.label(activity.name, 'warning', activity.project.customer.name ~ ': ' ~ activity.project.name) }} + {% set isVisible = activity.visible %} + {% if isVisible and not activity.project is null %} + {% set isVisible = activity.project.visible %} + {% if isVisible and not activity.project.customer is null %} + {% set isVisible = activity.project.customer.visible %} + {% endif %} {% endif %} + {% set label = '' %} + {% if not activity.project is null %} + {% set label = activity.project.customer.name ~ ': ' ~ activity.project.name %} + {% endif %} + {{ macro.label(activity.name, (isVisible ? 'primary' : 'warning'), label) }} {% endmacro %} {% macro label_project(project) %} diff --git a/templates/navbar/active-entries.html.twig b/templates/navbar/active-entries.html.twig index a4d56515..eeab67ff 100644 --- a/templates/navbar/active-entries.html.twig +++ b/templates/navbar/active-entries.html.twig @@ -21,7 +21,7 @@ {{ entry.activity.name }} {{ entry|duration }} -

{{ entry.activity.project.name }} ({{ entry.activity.project.customer.name }})

+

{{ entry.project.name }} ({{ entry.project.customer.name }})

{% endfor %} diff --git a/templates/navbar/recent-activities.html.twig b/templates/navbar/recent-activities.html.twig index 71892893..74f02451 100644 --- a/templates/navbar/recent-activities.html.twig +++ b/templates/navbar/recent-activities.html.twig @@ -1,17 +1,17 @@ -{% if activities is not empty %} +{% if entries is defined and entries is not empty %}