├── .bowerrc ├── .gitignore ├── .jshintrc ├── Gruntfile.js ├── LICENSE ├── README.md ├── bootstrap.js ├── bootstrap.prod.js ├── bower.json ├── index.html ├── karma.conf.js ├── package.json ├── src ├── .gitignore ├── app.js ├── conf │ └── trackr.js ├── css │ ├── notification.css │ ├── trackr.css │ └── trackr.less ├── i18n.js ├── jiraIssueCollector.js ├── modules │ ├── base │ │ ├── base.js │ │ ├── controllers │ │ │ ├── authorizationController.js │ │ │ ├── breadcrumbController.js │ │ │ ├── confirmationDialogController.js │ │ │ ├── controllers.js │ │ │ └── navigation.js │ │ ├── directives │ │ │ ├── directives.js │ │ │ ├── hasAuthority.js │ │ │ ├── tdNotification.js │ │ │ └── tdNotifications.js │ │ ├── filters │ │ │ ├── configValueFilter.js │ │ │ └── filters.js │ │ ├── partials │ │ │ ├── app.tpl.html │ │ │ ├── authorization.tpl.html │ │ │ ├── breadcrumbs.tpl.html │ │ │ ├── confirmationDialog.tpl.html │ │ │ ├── modules.tpl.html │ │ │ └── tdNotification.tpl.html │ │ └── services │ │ │ ├── confirmationDialogService.js │ │ │ ├── notificationService.js │ │ │ ├── services.js │ │ │ └── user.js │ ├── invoices │ │ ├── editController.js │ │ ├── index.tpl.html │ │ ├── indexController.js │ │ ├── invoicesModule.js │ │ ├── newController.js │ │ ├── newOrEdit.tpl.html │ │ └── top-menu.tpl.html │ ├── reportr │ │ ├── charts │ │ │ ├── barChartDirective.js │ │ │ ├── charts.css │ │ │ ├── chartsModule.js │ │ │ ├── colors.js │ │ │ └── pieChartDirective.js │ │ ├── employee-hours.tpl.html │ │ ├── employeeHoursController.js │ │ ├── expenses-debitor.tpl.html │ │ ├── expensesDebitorController.js │ │ ├── intervalLocationService.js │ │ ├── lodashHelpers.js │ │ ├── project-hours.tpl.html │ │ ├── projectHoursController.js │ │ ├── reportr.tpl.html │ │ ├── reportrModule.js │ │ ├── revenue.tpl.html │ │ ├── revenueController.js │ │ ├── sick-days.tpl.html │ │ ├── sickDaysController.js │ │ ├── sortHelper.js │ │ ├── top-menu.tpl.html │ │ ├── travel-expense.tpl.html │ │ ├── travelExpenseController.js │ │ ├── vacation.tpl.html │ │ └── vacationController.js │ ├── shared │ │ ├── PaginationLoader.js │ │ ├── controllers │ │ │ └── createOrUpdateModalController.js │ │ ├── directives │ │ │ ├── autosize.js │ │ │ ├── bsCheckbox.js │ │ │ ├── bsEdit.js │ │ │ ├── bsText.js │ │ │ ├── dateInterval.js │ │ │ ├── directives.js │ │ │ ├── errorDisplay.js │ │ │ ├── inlineDatepicker.js │ │ │ ├── pdfDownload.js │ │ │ └── tableSort.js │ │ ├── partials │ │ │ ├── bsCheckbox.tpl.html │ │ │ ├── bsEdit.tpl.html │ │ │ ├── bsText.tpl.html │ │ │ ├── createOrUpdateModal.tpl.html │ │ │ ├── dateInterval.tpl.html │ │ │ ├── inlineDatepicker.tpl.html │ │ │ └── pdfDownload.tpl.html │ │ ├── services │ │ │ └── createOrUpdateModalService.js │ │ └── shared.js │ └── trackr │ │ ├── administration │ │ ├── administration.tpl.html │ │ ├── administrationModule.js │ │ ├── companies │ │ │ ├── contactPersons │ │ │ │ ├── newOrEdit.tpl.html │ │ │ │ └── newOrEditController.js │ │ │ ├── display.tpl.html │ │ │ ├── displayController.js │ │ │ ├── editController.js │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ ├── newController.js │ │ │ └── newOrEdit.tpl.html │ │ ├── controllers.js │ │ ├── employees │ │ │ ├── addressEdit.tpl.html │ │ │ ├── addressEditController.js │ │ │ ├── adminEmployeeModule.js │ │ │ ├── display.tpl.html │ │ │ ├── displayController.js │ │ │ ├── editController.js │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ ├── newController.js │ │ │ └── newOrEdit.tpl.html │ │ └── projects │ │ │ ├── display.tpl.html │ │ │ ├── displayController.js │ │ │ ├── editController.js │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ ├── newController.js │ │ │ └── newOrEdit.tpl.html │ │ ├── directives │ │ ├── commentSection.js │ │ ├── commentSection.tpl.html │ │ └── directives.js │ │ ├── employee │ │ ├── address_book │ │ │ ├── addressBookController.js │ │ │ └── address_book.tpl.html │ │ ├── controllers.js │ │ ├── employee.tpl.html │ │ ├── employeeModule.js │ │ ├── expenses │ │ │ ├── edit.tpl.html │ │ │ ├── editController.js │ │ │ ├── employeeExpensesModule.js │ │ │ ├── expense-edit.tpl.html │ │ │ ├── expenseEditController.js │ │ │ ├── expenseNewController.js │ │ │ ├── expensesDecorator.js │ │ │ ├── expensesTable.tpl.html │ │ │ ├── expensesTableController.js │ │ │ ├── expensesTableDirective.js │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ ├── report-new.tpl.html │ │ │ └── reportNewController.js │ │ ├── self-edit.tpl.html │ │ ├── self.tpl.html │ │ ├── selfController.js │ │ ├── selfEditController.js │ │ ├── sick_days │ │ │ ├── sick-days-edit.tpl.html │ │ │ ├── sick-days.tpl.html │ │ │ ├── sickDaysController.js │ │ │ └── sickDaysEditController.js │ │ ├── timesheet │ │ │ ├── timesheet.tpl.html │ │ │ ├── timesheetController.js │ │ │ ├── timesheetOverview.tpl.html │ │ │ └── timesheetOverviewController.js │ │ └── vacation │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ ├── new.tpl.html │ │ │ └── newController.js │ │ ├── services │ │ ├── employeeService.js │ │ ├── services.js │ │ ├── travelExpenseReportService.js │ │ └── travelExpenseService.js │ │ ├── supervisor │ │ ├── billReport.tpl.html │ │ ├── billReportController.js │ │ ├── controllers.js │ │ ├── expenses │ │ │ ├── edit.tpl.html │ │ │ ├── editController.js │ │ │ ├── list.tpl.html │ │ │ ├── listController.js │ │ │ └── supervisorExpensesModule.js │ │ ├── fileBillableHours.tpl.html │ │ ├── fileBillableHoursController.js │ │ ├── fileBillableHoursSaveController.js │ │ ├── supervisor.tpl.html │ │ ├── supervisorModule.js │ │ ├── timeIntervalSetup.js │ │ └── vacation │ │ │ ├── vacation.tpl.html │ │ │ └── vacationController.js │ │ ├── top-menu.tpl.html │ │ ├── trackr.js │ │ └── welcome.tpl.html └── prodstarter.js ├── test ├── .jshintrc ├── backendMock.js ├── baseTestSetup.js ├── fixtures.js ├── modules │ ├── base │ │ ├── controllers │ │ │ └── navigationSpec.js │ │ ├── filters │ │ │ └── configValueFilterSpec.js │ │ └── services │ │ │ ├── confirmationServiceMock.js │ │ │ └── userSpec.js │ ├── invoices │ │ ├── indexControllerSpec.js │ │ └── newControllerSpec.js │ ├── reportr │ │ ├── charts │ │ │ └── colorsSpec.js │ │ ├── employeeHoursControllerSpec.js │ │ ├── intervalLocationServiceSpec.js │ │ ├── lodashHelpersSpec.js │ │ ├── projectHoursControllerSpec.js │ │ ├── revenueControllerSpec.js │ │ ├── sortHelperSpec.js │ │ ├── travelExpenseControllerSpec.js │ │ └── vacationControllerSpec.js │ ├── shared │ │ └── PaginationLoaderSpec.js │ └── trackr │ │ ├── administration │ │ ├── companies │ │ │ ├── displayControllerSpec.js │ │ │ ├── editControllerSpec.js │ │ │ ├── listControllerSpec.js │ │ │ └── newControllerSpec.js │ │ ├── employees │ │ │ ├── addressEditControllerSpec.js │ │ │ ├── displayControllerSpec.js │ │ │ ├── editControllerSpec.js │ │ │ ├── listControllerSpec.js │ │ │ └── newControllerSpec.js │ │ └── projects │ │ │ ├── displayControllerSpec.js │ │ │ ├── editControllerSpec.js │ │ │ ├── listControllerSpec.js │ │ │ └── newControllerSpec.js │ │ ├── employee │ │ ├── expenses │ │ │ ├── editControllerSpec.js │ │ │ ├── expenseEditControllerSpec.js │ │ │ ├── expenseNewControllerSpec.js │ │ │ ├── expensesDecoratorSpec.js │ │ │ ├── expensesTableControllerSpec.js │ │ │ ├── listControllerSpec.js │ │ │ └── reportNewControllerSpec.js │ │ ├── selfControllerSpec.js │ │ ├── selfEditControllerSpec.js │ │ ├── timesheet │ │ │ ├── timesheetControllerSpec.js │ │ │ └── timesheetOverviewControllerSpec.js │ │ └── vacation │ │ │ ├── listControllerSpec.js │ │ │ └── newControllerSpec.js │ │ ├── services │ │ └── employeeServiceSpec.js │ │ └── supervisor │ │ ├── billReportControllerSpec.js │ │ ├── expenses │ │ ├── editControllerSpec.js │ │ └── listControllerSpec.js │ │ ├── fileBillableHoursControllerSpec.js │ │ ├── fileBillableHoursSaveControllerSpec.js │ │ └── vacation │ │ └── vacationControllerSpec.js └── test-main.js └── trackr.json /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "src/vendor" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # npm 2 | node_modules/ 3 | 4 | # build 5 | dist/ 6 | reports/ 7 | 8 | # grunt concat 9 | .tmp/ 10 | 11 | # idea 12 | *.iml 13 | .idea/ 14 | atlassian-ide-plugin.xml -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 4, 3 | "camelcase": true, 4 | "curly": true, 5 | "freeze": true, 6 | "latedef": true, 7 | "quotmark": "single", 8 | "undef": true, 9 | "unused": true, 10 | "strict": true, 11 | "trailing": true, 12 | "jquery": true, 13 | "node": true, 14 | "globals": { 15 | "define": true 16 | } 17 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2014 techdev Solutions UG http://techdev.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | trackr-frontend 2 | ============== 3 | This is the trackr-page frontend. Though named trackr it's actually an app containing trackr. But trackr is most of it, code and featurewise. 4 | 5 | Building 6 | -------- 7 | You need node, npm installed. From npm you need grunt-cli, bower and karma. 8 | 9 | In the main directory run 10 | 11 | grunt test 12 | grunt dist 13 | grunt karma:cover 14 | 15 | Test will run all tests. dist will create the production package. karma:cover will run some coverage. The coverage report will be placed in reports/coverage. 16 | 17 | Running 18 | ------- 19 | The best way to run the frontend against a backend locally is to have a HTTP server like nginx running that serves the static files and proxies the requests to the backend. 20 | Here is my nginx config: 21 | 22 | 23 | server { 24 | listen 80; 25 | server_name localhost; 26 | 27 | location /api/ { 28 | proxy_pass http://localhost:8080/; 29 | } 30 | 31 | location / { 32 | alias /Users/moritz/workspace/techdev/trackr-frontend/dist/; 33 | try_files $uri $uri/ index.html =404; 34 | } 35 | 36 | } 37 | 38 | Of course you have to change the root path to your location. If you want to test the dist/ just add it to the root path. 39 | Put this in /etc/nginx/sites-available/trackr.conf, then make a symbolic link 40 | 41 | ln -s /etc/nginx/sites-avialable/trackr.conf /etc/nginx/sites-enabled/ 42 | 43 | And be sure that the sites-enabled are loaded in the nginx.conf. If you start nginx you can find trackr under http://localhost:9090/. 44 | -------------------------------------------------------------------------------- /bootstrap.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | baseUrl: 'src', 3 | paths: { 4 | 'jQuery': 'vendor/jquery/dist/jquery', 5 | 'twitter-bootstrap': 'vendor/bootstrap/dist/js/bootstrap', 6 | 'angular': 'vendor/angular/angular', 7 | 'angular-l10n-de': 'vendor/angular-i18n/angular-locale_de-de', 8 | 'angular-ui-router': 'vendor/angular-ui-router/release/angular-ui-router', 9 | 'restangular': 'vendor/restangular/dist/restangular', 10 | 'lodash': 'vendor/lodash/lodash', 11 | 'angular-translate': 'vendor/angular-translate/angular-translate', 12 | 'angular-translate-loader-url': 'vendor/angular-translate-loader-url/angular-translate-loader-url', 13 | 'angular-ui': 'vendor/angular-ui-bootstrap-bower/ui-bootstrap-tpls', 14 | 'moment': 'vendor/moment/moment', 15 | 'chartjs': 'vendor/chartjs/Chart', 16 | 'randomColor': 'vendor/randomColor/randomColor', 17 | 'configuration': 'conf/trackr' 18 | }, 19 | shim: { 20 | 'angular': { exports: 'angular', deps: ['jQuery'] }, 21 | 'angular-l10n-de': { deps: ['angular'] }, 22 | 'angular-ui-router': { deps: ['angular']}, 23 | 'jQuery': { exports: '$' }, 24 | 'twitter-bootstrap': { deps: ['jQuery'] }, 25 | 'restangular': { deps: ['angular', 'lodash']}, 26 | 'lodash': { exports: '_'}, 27 | 'angular-translate': { deps: ['angular'] }, 28 | 'angular-translate-loader-url': { deps: ['angular-translate'] }, 29 | 'angular-ui': { deps: ['angular'] } 30 | } 31 | }); 32 | 33 | var dependencies = ['angular', 'app', 'angular-l10n-de']; 34 | require(dependencies, function(angular) { 35 | 'use strict'; 36 | angular.bootstrap(document, ['app']); 37 | }); 38 | -------------------------------------------------------------------------------- /bootstrap.prod.js: -------------------------------------------------------------------------------- 1 | require.config({ 2 | paths: { 3 | 'jQuery': 'src/vendor/jquery/dist/jquery.min', 4 | 'twitter-bootstrap': 'src/vendor/bootstrap/dist/js/bootstrap.min', 5 | 'angular': 'src/vendor/angular/angular.min', 6 | 'angular-l10n-de': 'src/vendor/angular-i18n/angular-locale_de-de', 7 | 'angular-ui-router': 'src/vendor/angular-ui-router/release/angular-ui-router.min', 8 | 'restangular': 'src/vendor/restangular/dist/restangular.min', 9 | 'lodash': 'src/vendor/lodash/lodash.min', 10 | 'angular-translate': 'src/vendor/angular-translate/angular-translate.min', 11 | 'angular-translate-loader-url': 'src/vendor/angular-translate-loader-url/angular-translate-loader-url.min', 12 | 'angular-ui': 'src/vendor/angular-ui-bootstrap-bower/ui-bootstrap-tpls.min', 13 | 'moment': 'src/vendor/moment/min/moment.min', 14 | 'chartjs': 'src/vendor/chartjs/Chart.min', 15 | 'randomColor': 'src/vendor/randomColor/randomColor', 16 | 'configuration': 'conf/trackr' 17 | }, 18 | shim: { 19 | 'angular': { exports: 'angular', deps: ['jQuery'] }, 20 | 'angular-l10n-de': { deps: ['angular'] }, 21 | 'angular-ui-router': { deps: ['angular']}, 22 | 'jQuery': { exports: '$' }, 23 | 'twitter-bootstrap': { deps: ['jQuery'] }, 24 | 'restangular': { deps: ['angular', 'lodash']}, 25 | 'lodash': { exports: '_'}, 26 | 'angular-translate': { deps: ['angular'] }, 27 | 'angular-translate-loader-url': { deps: ['angular-translate'] }, 28 | 'angular-ui': { deps: ['angular'] } 29 | } 30 | }); 31 | require(['trackr']); -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trackr-frontend", 3 | "version": "1.0.0", 4 | "authors": [ 5 | "Moritz Schulze " 6 | ], 7 | "description": "Trackr frontend", 8 | "keywords": [ 9 | "angular" 10 | ], 11 | "license": "MIT", 12 | "private": true, 13 | "ignore": [ 14 | "**/.*", 15 | "node_modules", 16 | "bower_components", 17 | "test", 18 | "tests" 19 | ], 20 | "dependencies": { 21 | "bootstrap": "3.2.0", 22 | "angular": "1.4.8", 23 | "requirejs": "2.1.14", 24 | "restangular": "1.5.1", 25 | "lodash": "3.10.1", 26 | "angular-ui-router": "0.2.10", 27 | "jquery": "git://github.com/jquery/jquery.git#2.1.1", 28 | "angular-translate": "~2.2.0", 29 | "angular-ui-bootstrap-bower": "0.14.3", 30 | "angular-translate-loader-url": "2.2.0", 31 | "moment": "~2.8.1", 32 | "angular-i18n": "1.4.8", 33 | "chartjs": "~1.0.2", 34 | "randomColor": "~0.2.0" 35 | }, 36 | "devDependencies": { 37 | "angular-mocks": "1.4.8" 38 | }, 39 | "resolutions": { 40 | "angular": "1.4.8" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | trackr 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trackr-frontend", 3 | "version": "1.0.0", 4 | "author": "Moritz Schulze ", 5 | "private": true, 6 | "keywords": [ 7 | "angular", 8 | "frontend" 9 | ], 10 | "licenses": "MIT", 11 | "description": "Trackr frontend", 12 | "devDependencies": { 13 | "grunt": "0.4.5", 14 | "grunt-contrib-clean": "0.5.0", 15 | "grunt-contrib-concat": "0.3.0", 16 | "grunt-contrib-copy": "0.5.0", 17 | "grunt-contrib-cssmin": "0.7.0", 18 | "grunt-contrib-htmlmin": "0.1.3", 19 | "grunt-contrib-jshint": "0.8.0", 20 | "grunt-contrib-requirejs": "0.4.4", 21 | "grunt-contrib-uglify": "0.2.7", 22 | "grunt-contrib-watch": "0.5.3", 23 | "grunt-karma": "0.8.3", 24 | "grunt-processhtml": "0.2.9", 25 | "grunt-usemin": "2.0.2", 26 | "karma": "0.12.x", 27 | "karma-chrome-launcher": "0.1.10", 28 | "karma-coverage": "0.2.7", 29 | "karma-firefox-launcher": "0.1.6", 30 | "karma-jasmine": "0.1.5", 31 | "karma-junit-reporter": "0.2.2", 32 | "karma-phantomjs-launcher": "0.1.4", 33 | "karma-requirejs": "0.2.2", 34 | "karma-teamcity-reporter": "0.1.2", 35 | "requirejs": "2.1.17" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | # bower components 2 | vendor/ -------------------------------------------------------------------------------- /src/conf/trackr.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | return { 3 | "portalUrl": "http://localhost:8081/" 4 | } 5 | }); 6 | -------------------------------------------------------------------------------- /src/css/notification.css: -------------------------------------------------------------------------------- 1 | #notification-container { 2 | position: fixed; 3 | top: 0; 4 | width: 100%; 5 | text-align: center; 6 | margin: 0 auto; 7 | height: 0; 8 | 9 | z-index: 9999; 10 | } 11 | 12 | #notification-container ul { 13 | width: 450px; 14 | margin: 0 auto; 15 | list-style: none outside; 16 | } 17 | 18 | #notification-container li { 19 | color: #555555; 20 | 21 | background-color: #FAFAFA; 22 | background-repeat: repeat-x; 23 | 24 | margin: 20px 0; 25 | padding: 0; 26 | text-align: left; 27 | text-shadow: rgba(255, 255, 255, 0.75) 0 1px 0; 28 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 15px rgba(0, 0, 0, 0.2); 29 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 15px rgba(0, 0, 0, 0.2); 30 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 15px rgba(0, 0, 0, 0.2); 31 | 32 | border: 1px solid #d5d5d5; 33 | border-radius: 5px; 34 | } 35 | 36 | #notification-container li > div { 37 | padding: 10px 15px 12px 15px; 38 | min-height: 24px; 39 | } 40 | 41 | #notification-container li.info { 42 | background-color: #cae5ff; 43 | color: #004d7f; 44 | border-color: #557393; 45 | } 46 | 47 | #notification-container li.success { 48 | background-color: #ceecc3; 49 | color: #0f9000; 50 | border-color: #10a000; 51 | } 52 | 53 | #notification-container li.error { 54 | background-color: #ffd0d0; 55 | color: #a54545; 56 | border-color: #a54545; 57 | } 58 | 59 | #notification-container li.fatal { 60 | background-color: #3f3f3f; 61 | color: #fdfdfd; 62 | border-color: #000000; 63 | text-shadow: black 0 1px 0; 64 | } 65 | 66 | #notification-container li .close { 67 | font-size: 14px; 68 | float: right; 69 | position: relative; 70 | left: -3px; 71 | top: 3px; 72 | visibility: hidden; 73 | } 74 | 75 | #notification-container li:hover .close { 76 | visibility: visible; 77 | } -------------------------------------------------------------------------------- /src/css/trackr.css: -------------------------------------------------------------------------------- 1 | #page-wrapper { 2 | margin-left: 30px; 3 | margin-right: 30px; 4 | } 5 | .clickable { 6 | /* clickable elements should have cursor hand */ 7 | 8 | cursor: pointer; 9 | } 10 | .panel .table-bordered { 11 | /* overrides '.table-dynamic .table-bordered' in 'main.css' */ 12 | 13 | border-top: 1px solid #ddd; 14 | border-bottom: 1px solid #ddd; 15 | } 16 | .table-footer { 17 | /* overrides '.table-dynamic .table-footer' in 'main.css' */ 18 | 19 | margin: 0; 20 | padding: 8px; 21 | } 22 | .table-footer ul.pagination { 23 | /* no top/bottom margin on pagination elements if they're in a table footer */ 24 | 25 | margin-top: 0; 26 | margin-bottom: 0; 27 | } 28 | h1 { 29 | margin-top: 0; 30 | margin-bottom: 0; 31 | } 32 | .form-group .checkbox label { 33 | font-weight: bold; 34 | } 35 | .control-label { 36 | text-transform: uppercase; 37 | color: #aaaaaa; 38 | font-weight: normal; 39 | } 40 | inline-datepicker .input-group .form-control { 41 | width: 100px; 42 | } 43 | inline-datepicker .input-group .input-group-btn button { 44 | height: 34px; 45 | } 46 | .form-group .help-block { 47 | display: none; 48 | } 49 | .form-group.has-error .help-block { 50 | display: block; 51 | } 52 | body { 53 | padding-top: 70px; 54 | } 55 | th.sortable a { 56 | cursor: pointer; 57 | } 58 | th.sortable.sort-asc a:after { 59 | font-family: "Glyphicons Halflings"; 60 | display: inline-block; 61 | font-weight: 200; 62 | content: "\e072"; 63 | float: right; 64 | color: #aaaaaa; 65 | transform: rotate(270deg); 66 | font-size: 10px; 67 | } 68 | th.sortable.sort-desc a:after { 69 | font-family: "Glyphicons Halflings"; 70 | font-weight: 200; 71 | display: inline-block; 72 | content: "\e072"; 73 | float: right; 74 | color: #aaaaaa; 75 | transform: rotate(90deg); 76 | font-size: 10px; 77 | } 78 | -------------------------------------------------------------------------------- /src/i18n.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | 'use strict'; 3 | return { 4 | setLanguageForUser: function ($translate, trackrUser) { 5 | $translate.use(trackrUser.locale); 6 | } 7 | }; 8 | }); -------------------------------------------------------------------------------- /src/jiraIssueCollector.js: -------------------------------------------------------------------------------- 1 | define(['jQuery', 'configuration'], function(jQuery, config) { 2 | 'use strict'; 3 | return function() { 4 | function loadIssueCollector(url) { 5 | return jQuery.ajax({ 6 | url: url, 7 | type: 'get', 8 | cache: true, 9 | dataType: 'script' 10 | }); 11 | } 12 | 13 | if(config.jiraIssueCollectorUrl) { 14 | loadIssueCollector(config.jiraIssueCollectorUrl); 15 | } 16 | }; 17 | }); 18 | -------------------------------------------------------------------------------- /src/modules/base/controllers/authorizationController.js: -------------------------------------------------------------------------------- 1 | /* global localStorage */ 2 | /* jshint camelcase: false */ 3 | define([], function() { 4 | 'use strict'; 5 | return ['$scope', '$location', '$state', '$log', '$http', function($scope, $location, $state, $log, $http) { 6 | // Set the return_to uri to the current location, i.e. the 'authorize' page. 7 | $scope.returnToUri = $location.absUrl(); 8 | 9 | var hash = $location.hash(); 10 | if(hash.indexOf('access_token') > -1) { 11 | var extractRegex = /access_token=([a-zA-Z0-9-]*)&/; 12 | var result = extractRegex.exec(hash); 13 | if(result.length > 1) { 14 | $log.debug('Found access token in hash, redirecting to the app.', result[1]); 15 | localStorage.setItem('oauthToken', result[1]); 16 | $http.defaults.headers.common.Authorization = 'Bearer ' + result[1]; 17 | $state.go('app'); 18 | } else { 19 | $log.error('access_token not extractable via regex from hash', hash); 20 | } 21 | } else if(hash.length > 0) { 22 | $log.debug('Hash didn\'t contain the access_token', hash); 23 | } 24 | }]; 25 | }); -------------------------------------------------------------------------------- /src/modules/base/controllers/breadcrumbController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$scope', '$state', '$log', function($scope, $state, $log) { 4 | function getBreadcrumbChain(state) { 5 | if(state.self.url === '^') { 6 | //This is the root state of the state provider. 7 | return []; 8 | } 9 | 10 | var breadcrumb = { 11 | translateCode: state.self.breadcrumbTranslateCode, 12 | //Every module has a abstract parent state and a home state. Since we can't ui-sref to the abstract 13 | //state we use the home state. Of course this only works under the given assumptions. 14 | stateName: state.self.abstract ? state.self.name + '.home' : state.self.name 15 | }; 16 | 17 | if(breadcrumb.translateCode === undefined) { 18 | $log.debug('State without breadcrumb translate code', breadcrumb.stateName); 19 | } 20 | 21 | return [breadcrumb].concat(getBreadcrumbChain(state.parent)); 22 | } 23 | 24 | function putBreadcrumbsInScope(state) { 25 | var breadcrumbs = getBreadcrumbChain(state); 26 | $scope.breadcrumbs = breadcrumbs.reverse(); 27 | } 28 | 29 | $scope.$on('$stateChangeSuccess', function() { 30 | putBreadcrumbsInScope($state.$current); 31 | }); 32 | }]; 33 | }); -------------------------------------------------------------------------------- /src/modules/base/controllers/confirmationDialogController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$scope', 'translationCode', '$uibModalInstance', function($scope, translationCode, $modalInstance) { 4 | $scope.translationCode = translationCode; 5 | 6 | $scope.cancel = function() { 7 | $modalInstance.dismiss(); 8 | }; 9 | 10 | $scope.ok = function() { 11 | $modalInstance.close(); 12 | }; 13 | }]; 14 | }); -------------------------------------------------------------------------------- /src/modules/base/controllers/controllers.js: -------------------------------------------------------------------------------- 1 | define(['modules/base/controllers/navigation', 'modules/base/controllers/breadcrumbController', 2 | 'modules/base/controllers/confirmationDialogController', 'modules/base/controllers/authorizationController'], 3 | function(NavigationController, BreadcrumbController, ConfirmationDialogController, AuthorizationController) { 4 | 'use strict'; 5 | return { 6 | init: function(module) { 7 | module.controller('base.controllers.navigation', NavigationController); 8 | module.controller('base.controllers.breadcrumb-controller', BreadcrumbController); 9 | module.controller('base.controllers.confirmation-dialog', ConfirmationDialogController); 10 | module.controller('base.controllers.authorization', AuthorizationController); 11 | } 12 | }; 13 | }); -------------------------------------------------------------------------------- /src/modules/base/controllers/navigation.js: -------------------------------------------------------------------------------- 1 | /* global localStorage */ 2 | define([], function () { 3 | 'use strict'; 4 | return ['$scope', '$translate', 'base.services.user', '$http', '$state', '$log', function ($scope, $translate, UserService, $http, $state, $log) { 5 | $scope.user = UserService.getUser(); 6 | 7 | $scope.logout = function() { 8 | $log.debug('User pressed logout, deleting token and transition to authorize state'); 9 | $http.defaults.headers.common.Authorization = null; 10 | localStorage.removeItem('oauthToken'); 11 | $state.go('authorize'); 12 | }; 13 | 14 | $scope.changeLanguage = function (languageCode) { 15 | $http.put('api/translations', {}, {params: {locale: languageCode}}).then(function() { 16 | $translate.use(languageCode); 17 | }); 18 | }; 19 | }]; 20 | }); 21 | -------------------------------------------------------------------------------- /src/modules/base/directives/directives.js: -------------------------------------------------------------------------------- 1 | define(['modules/base/directives/hasAuthority', 2 | 'modules/base/directives/tdNotifications', 3 | 'modules/base/directives/tdNotification'], function(hasAuthority, tdNotifications, tdNotification) { 4 | 'use strict'; 5 | return { 6 | init: function(module) { 7 | module.directive('hasAuthority', hasAuthority); 8 | module.directive('tdNotifications', tdNotifications); 9 | module.directive('tdNotification', tdNotification); 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /src/modules/base/directives/hasAuthority.js: -------------------------------------------------------------------------------- 1 | //Idea from http://nadeemkhedr.wordpress.com/2013/11/25/how-to-do-authorization-and-role-based-permissions-in-angularjs/ 2 | define([], function () { 3 | 'use strict'; 4 | return ['base.services.user', function (UserService) { 5 | return { 6 | link: function (scope, element, attrs) { 7 | var value = attrs.hasAuthority.trim(); 8 | var notAuthorityFlag = value[0] === '!'; 9 | if (notAuthorityFlag) { 10 | value = value.slice(1).trim(); 11 | } 12 | 13 | function toggleVisibilityBasedOnAuthority() { 14 | var hasAuthority = UserService.userHasAuthority(value); 15 | 16 | if (hasAuthority && !notAuthorityFlag || !hasAuthority && notAuthorityFlag) { 17 | element.show(); 18 | } 19 | else { 20 | element.hide(); 21 | } 22 | } 23 | toggleVisibilityBasedOnAuthority(); 24 | } 25 | }; 26 | }]; 27 | }); -------------------------------------------------------------------------------- /src/modules/base/directives/tdNotification.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | /** 4 | * One notification element. 5 | */ 6 | return [function() { 7 | return { 8 | restrict: 'A', 9 | templateUrl: 'src/modules/base/partials/tdNotification.tpl.html', 10 | link: function(scope, element) { 11 | element.addClass(scope.notification.level); 12 | element.fadeIn(300).delay(scope.notification.duration).slideUp(300, function() { 13 | element.remove(); 14 | }); 15 | 16 | scope.close = function() { 17 | element.clearQueue(); 18 | element.slideUp(300, function() { 19 | element.remove(); 20 | }); 21 | }; 22 | } 23 | }; 24 | }]; 25 | }); -------------------------------------------------------------------------------- /src/modules/base/directives/tdNotifications.js: -------------------------------------------------------------------------------- 1 | define(['angular'], function(angular) { 2 | 'use strict'; 3 | /** 4 | * Container for notifications. Appends notifications to itself on the event "td.notifiy". 5 | */ 6 | return ['$compile', 'base.services.notification', function($compile, NotificationService) { 7 | return { 8 | restrict: 'A', 9 | link: function(scope, element) { 10 | NotificationService.onNotification(function(notification) { 11 | var $notification = angular.element('
  • '); 12 | var childScope = scope.$new(); 13 | childScope.notification = notification; 14 | $compile($notification)(childScope); 15 | element.append($notification); 16 | }); 17 | } 18 | }; 19 | }]; 20 | }); -------------------------------------------------------------------------------- /src/modules/base/filters/configValueFilter.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | 'use strict'; 3 | function configValueFilter(configuration) { 4 | return function (configKey) { 5 | if (configuration.hasOwnProperty(configKey)) { 6 | return configuration[configKey]; 7 | } else { 8 | throw new Error('Configuration key ' + configKey + ' not found.'); 9 | } 10 | }; 11 | } 12 | 13 | configValueFilter.$inject = ['base.config']; 14 | return configValueFilter; 15 | }); -------------------------------------------------------------------------------- /src/modules/base/filters/filters.js: -------------------------------------------------------------------------------- 1 | define(['./configValueFilter'], function (configValue) { 2 | 'use strict'; 3 | 4 | return { 5 | init: function(module) { 6 | module.filter('configValue', configValue); 7 | } 8 | }; 9 | }); -------------------------------------------------------------------------------- /src/modules/base/partials/app.tpl.html: -------------------------------------------------------------------------------- 1 | 18 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 |
      35 |
    36 |
    -------------------------------------------------------------------------------- /src/modules/base/partials/authorization.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 |
    6 |
    7 |
    8 |

    9 | No authentication found. Please click the following link to authenticate. 10 |

    11 | Authenticate 13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    -------------------------------------------------------------------------------- /src/modules/base/partials/breadcrumbs.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/base/partials/confirmationDialog.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/modules/base/partials/modules.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    Bitte Modul auswählen

    4 | trackr 5 | Invoices 6 | reportr 7 |
    8 |
    -------------------------------------------------------------------------------- /src/modules/base/partials/tdNotification.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
    {{notification.text}}
    -------------------------------------------------------------------------------- /src/modules/base/services/confirmationDialogService.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$uibModal', function($modal) { 4 | return { 5 | /** 6 | * Opens a confirmation dialog with the buttons ok/cancel and the text translated by the translate code. 7 | * @param translateCode The text to insert into the modal, given by a translation code. 8 | * @returns {*} The $modalInstance 9 | */ 10 | openConfirmationDialog: function(translateCode) { 11 | return $modal.open({ 12 | backdrop: 'static', 13 | templateUrl: 'src/modules/base/partials/confirmationDialog.tpl.html', 14 | controller: 'base.controllers.confirmation-dialog', 15 | resolve: { 16 | translationCode: function() { 17 | return translateCode; 18 | } 19 | } 20 | }); 21 | } 22 | }; 23 | }]; 24 | }); -------------------------------------------------------------------------------- /src/modules/base/services/notificationService.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return [function() { 4 | var callback; 5 | function displayNotification(text, level, duration) { 6 | var _duration = duration || 5000; 7 | var notification = { 8 | text: text, 9 | duration: _duration, 10 | level: level 11 | }; 12 | if(callback) { 13 | callback(notification); 14 | } 15 | } 16 | return { 17 | onNotification: function(_callback) { 18 | callback = _callback; 19 | }, 20 | 21 | /** 22 | * Display an info message to the user. 23 | * @param text The text to display 24 | * @param [duration] The duration, defaults to 5 seconds. 25 | */ 26 | info: function(text, duration) { 27 | displayNotification(text, 'info', duration); 28 | }, 29 | 30 | /** 31 | * Display an error message to the user. 32 | * @param text The text to display 33 | * @param [duration] The duration, defaults to 5 seconds. 34 | */ 35 | error: function(text, duration) { 36 | displayNotification(text, 'error', duration); 37 | }, 38 | 39 | /** 40 | * Display a fatal message to the user. 41 | * @param text The text to display 42 | * @param [duration] The duration, defaults to 5 seconds. 43 | */ 44 | fatal: function(text, duration) { 45 | displayNotification(text, 'fatal', duration); 46 | } 47 | }; 48 | }]; 49 | }); -------------------------------------------------------------------------------- /src/modules/base/services/services.js: -------------------------------------------------------------------------------- 1 | define(['modules/base/services/user', 'modules/base/services/notificationService', 'modules/base/services/confirmationDialogService'], 2 | function(user, notificationService, confirmationService) { 3 | 'use strict'; 4 | return { 5 | init: function(module) { 6 | module.service('base.services.user', user); 7 | module.service('base.services.notification', notificationService); 8 | module.service('base.services.confirmation-dialog', confirmationService); 9 | } 10 | }; 11 | }); -------------------------------------------------------------------------------- /src/modules/base/services/user.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return [function() { 4 | var user; 5 | var permissionLevels = {//TODO load this from the server 6 | ROLE_ADMIN: 0, 7 | ROLE_SUPERVISOR: 1, 8 | ROLE_EMPLOYEE: 2 9 | }; 10 | return { 11 | setUser: function(_user) { 12 | user = _user; 13 | if(user) { 14 | user.highestAuthority = user.authorities.sort(function(a1, a2) { 15 | return permissionLevels[a1.authority] - permissionLevels[a2.authority]; 16 | })[0]; 17 | } 18 | }, 19 | 20 | getUser: function() { 21 | return user; 22 | }, 23 | 24 | /** 25 | * Check if the current user has a higher or equal authority than the argument 26 | * @param authority The authority to check for 27 | * @returns {boolean} True if the user authority is higher, e.g. the user is ROLE_SUPERVISOR and the argument is ROLE_EMPLOYEE. 28 | */ 29 | userHasAuthority: function(authority) { 30 | if(user.authorities.length === 0) { 31 | return false; 32 | } 33 | var userAuthority = user.highestAuthority.authority; 34 | return permissionLevels[userAuthority] <= permissionLevels[authority]; 35 | } 36 | }; 37 | }]; 38 | }); -------------------------------------------------------------------------------- /src/modules/invoices/invoicesModule.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'modules/invoices/indexController', 'modules/invoices/newController', 'modules/invoices/editController'], 2 | function(angular, IndexController, NewController, EditController) { 3 | 'use strict'; 4 | var configFn = []; 5 | var invoices = angular.module('invoices', configFn); 6 | invoices.controller('invoices.controllers.index', IndexController); 7 | invoices.controller('invoices.controllers.new', NewController); 8 | invoices.controller('invoices.controllers.edit', EditController); 9 | invoices.config(['$stateProvider', function($stateProvider) { 10 | $stateProvider. 11 | state('app.invoices', { 12 | url: 'invoices', 13 | views: { 14 | 'top-menu@app': { 15 | templateUrl: 'src/modules/invoices/top-menu.tpl.html', 16 | controller: 'base.controllers.navigation' 17 | }, 18 | 'center@app': { 19 | templateUrl: 'src/modules/invoices/index.tpl.html', 20 | controller: 'invoices.controllers.index' 21 | } 22 | }, 23 | needsAuthority: 'ROLE_ADMIN' 24 | }); 25 | }]); 26 | return invoices; 27 | }); -------------------------------------------------------------------------------- /src/modules/invoices/top-menu.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/reportr/charts/charts.css: -------------------------------------------------------------------------------- 1 | .chart-legend { 2 | position: absolute; 3 | top: 20px; 4 | } 5 | 6 | .chart-legend ul { 7 | list-style: none; 8 | } 9 | 10 | .chart-legend ul li { 11 | margin: 2px 0; 12 | } 13 | 14 | .chart-legend ul li span { 15 | float: left; 16 | width: 17px; 17 | margin-right: 5px 18 | } 19 | 20 | .piechart canvas { 21 | margin-right: 20px; 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/reportr/charts/chartsModule.js: -------------------------------------------------------------------------------- 1 | define(['angular', './pieChartDirective', './barChartDirective'], function(angular, pieChartDirective, barChartDirective) { 2 | 'use strict'; 3 | var charts = angular.module('charts', []); 4 | charts.directive('pieChart', pieChartDirective); 5 | charts.directive('barChart', barChartDirective); 6 | return charts; 7 | }); -------------------------------------------------------------------------------- /src/modules/reportr/charts/colors.js: -------------------------------------------------------------------------------- 1 | define(['randomColor'], function(randomColor) { 2 | 'use strict'; 3 | 4 | function increaseBrightness(hex, percent) { 5 | // strip the leading # if it's there 6 | hex = hex.replace(/^\s*#|\s*$/g, ''); 7 | 8 | var r = parseInt(hex.substr(0, 2), 16), 9 | g = parseInt(hex.substr(2, 2), 16), 10 | b = parseInt(hex.substr(4, 2), 16); 11 | 12 | return '#' + 13 | ((0 | (1 << 8) + r + (256 - r) * percent / 100).toString(16)).substr(1) + 14 | ((0 | (1 << 8) + g + (256 - g) * percent / 100).toString(16)).substr(1) + 15 | ((0 | (1 << 8) + b + (256 - b) * percent / 100).toString(16)).substr(1); 16 | } 17 | 18 | function getColorsWithHighlight(count) { 19 | return randomColor({count: count}) 20 | .map(function(hex) { 21 | return { 22 | color: hex, 23 | highlight: increaseBrightness(hex, 20) 24 | }; 25 | }); 26 | } 27 | 28 | return { 29 | getColorWithHighlight: getColorsWithHighlight 30 | }; 31 | }); -------------------------------------------------------------------------------- /src/modules/reportr/employee-hours.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
    13 | 14 | 16 | 17 |
    {{workTimeData[0]}}{{workTimeData[1]}}
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/reportr/expenses-debitor.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
    13 | 14 | 16 | 17 |
    {{travelExpenseReportData[0]}}{{ travelExpenseReportData[1] | currency:'€' }}
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/reportr/intervalLocationService.js: -------------------------------------------------------------------------------- 1 | define(['moment', 'angular'], function(moment, angular) { 2 | 'use strict'; 3 | var intervalLocationService = function($location) { 4 | 5 | /** 6 | * Loads a time interval from the URL search from the parameters 'start' and 'end'. If not found 7 | * the first day of month resp. last day of month is returned. 8 | * 9 | * @returns {{start: *, end: *}} 10 | */ 11 | this.loadIntervalFromLocation = function() { 12 | var interval = { 13 | start: moment().startOf('month').toDate(), 14 | end: moment().endOf('month').toDate() 15 | }; 16 | var search = $location.search(); 17 | if(angular.isString(search.start)) { 18 | interval.start = moment(search.start).toDate(); 19 | } 20 | if(angular.isString(search.end)) { 21 | interval.end = moment(search.end).toDate(); 22 | } 23 | return interval; 24 | }; 25 | 26 | /** 27 | * Save an interval into the URL search (format YYYY-MM-DD) in the parameters 'start' and 'end'. 28 | * @param start {Date} The start for the interval. 29 | * @param end {Date} The end for the interval. 30 | */ 31 | this.saveIntervalToLocation = function(start, end) { 32 | $location.search('start', moment(start).format('YYYY-MM-DD')); 33 | $location.search('end', moment(end).format('YYYY-MM-DD')); 34 | }; 35 | }; 36 | intervalLocationService.$inject = ['$location']; 37 | return intervalLocationService; 38 | }); -------------------------------------------------------------------------------- /src/modules/reportr/lodashHelpers.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return { 4 | /** 5 | * Maps all values in array with mapper and reduces the mapped values to a sum with numberExtractor. 6 | * 7 | * The map is turned into an array of pairs for the return value since this is the format that is desired for reportr (it is sortable which 8 | * a map is not). 9 | * 10 | * Example: 11 | * Input: [ { name: 'name1', number: 1 }, { name: 'name1', number: 2 }, { name: 'name2', number: 1 } ] 12 | * with the obvious mappings 13 | * Output: [ ['name1', 3], ['name2', 1] ] 14 | * @param {Array} array The values to map 15 | * @param {Function|String} mapper A function from an array element to a value object like string, int etc or the name of a property to use. 16 | * @param {Function|String} numberExtractor A function from an array element to a number or the name of a property of an value that is a number. 17 | * @return {Array} An array consisting of 'pairs' (arrays of length 2), The first element is the groupBy key, the second element the sum of numbers. 18 | */ 19 | mapAndReduceValuesToSum: function(array, mapper, numberExtractor) { 20 | var _numberExtractor = numberExtractor; 21 | if (_.isString(numberExtractor)) { 22 | _numberExtractor = function(val) { 23 | return val[numberExtractor]; 24 | }; 25 | } 26 | return _.pairs(_.mapValues( 27 | _.groupBy(array, mapper), 28 | function(values) { 29 | return _.reduce(values, function(sum, val) { 30 | return sum + _numberExtractor(val); 31 | }, 0); 32 | } 33 | )); 34 | } 35 | }; 36 | }); -------------------------------------------------------------------------------- /src/modules/reportr/project-hours.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
    13 | 14 | 16 | 17 | 19 | 20 |
    {{projectTimesData[0]}}{{projectTimesData[1]}}{{projectTimesData[2]}}
    28 |
    29 |
    30 |
    31 |
    32 | 33 |
    34 |
    -------------------------------------------------------------------------------- /src/modules/reportr/reportr.tpl.html: -------------------------------------------------------------------------------- 1 |

    reportr

    -------------------------------------------------------------------------------- /src/modules/reportr/revenue.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | : {{ totalRevenue | currency:'€' }} 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 |
    19 | 20 | 22 | 23 |
    {{ invoiceData[0] }}{{ invoiceData[1] | currency:'€' }}
    30 |
    31 |
    32 |
    33 |
    34 | 35 |
    36 |
    37 | -------------------------------------------------------------------------------- /src/modules/reportr/sick-days.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
    13 | 14 | 16 | 17 |
    {{sickDaysData[0]}}{{sickDaysData[1]}}
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/reportr/sortHelper.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return { 4 | /** 5 | * Sort an array of the form 6 | * [ 7 | * ['string1', number1, number2, ...], 8 | * ['string2', number3, number4, ...], 9 | * ... 10 | * ] 11 | * by one of the indices of the child arrays. If the index is 0 the case is ignored. 12 | * 13 | * Uses JS sort and thus mutates the array 14 | * @param {Array} array The array of the special sort to sort 15 | * @param {String|Number} index The index to use for sorting 16 | * @param {Number} direction -1 ascending, 1 descending 17 | */ 18 | sortArrayOfArrays: function(array, index, direction) { 19 | var before = _.identity; 20 | if(index === 0 || index === '0') { 21 | before = function(a) { 22 | return a.toLowerCase(); 23 | }; 24 | } 25 | array.sort(function(a, b) { 26 | if(before(a[index]) < before(b[index])) { 27 | return direction; 28 | } else if(before(a[index]) > before(b[index])) { 29 | return direction*-1; 30 | } 31 | return 0; 32 | }); 33 | } 34 | }; 35 | }); -------------------------------------------------------------------------------- /src/modules/reportr/top-menu.tpl.html: -------------------------------------------------------------------------------- 1 | 23 | -------------------------------------------------------------------------------- /src/modules/reportr/travel-expense.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
    13 | 14 | 16 | 17 |
    {{travelExpenseReportData[0]}}{{ travelExpenseReportData[1] | currency:'€' }}
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/reportr/vacation.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 |
    9 |
    10 | 11 | 12 | 15 | 18 | 19 | 20 | 21 | 22 | 23 |
    13 | 14 | 16 | 17 |
    {{vacationRequestData[0]}}{{vacationRequestData[1]}}
    24 |
    25 |
    26 |
    27 |
    28 | 29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/shared/PaginationLoader.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | /** 4 | * A helper object that loads paginated objects and puts them into a scope. 5 | * @param base The Restangular base object obtained e.g. by 'Restangular.all("projects")'. 6 | * @param name The name that the objects fetched should have in the $scope. 7 | * @param sort The sorting parameter, e.g. 'name,asc' 8 | * @param $scope The $scope to put the fetched objects in. 9 | * @param [size] Size to load, standard 5. 10 | * @constructor 11 | */ 12 | function PaginationLoader(base, name, sort, $scope, size) { 13 | var self = this; 14 | this.base = base; 15 | this.name = name; 16 | this.sort = sort; 17 | this.$scope = $scope; 18 | this.size = size || 5; 19 | this.afterObjectsGet = function(objects) { 20 | self.$scope[self.name] = objects; 21 | }; 22 | } 23 | 24 | /** 25 | * Load the given page from the server and calls the afterObjectsGet method with it. 26 | * @param {Number} [page] The page to load, 1-based. 27 | * @param {Object} [otherQueryParams] Other params to be passed to Restangular.getList(). 28 | * @param {*} [userData] Userdata passed to the afterObjectsGet method. 29 | * @param {String} [sort] Sort that should be used 30 | */ 31 | PaginationLoader.prototype.loadPage = function(page, otherQueryParams, userData, sort) { 32 | page = page || 1; 33 | var self = this; 34 | if(sort) { 35 | this.sort = sort; 36 | } 37 | var parameters = {sort: this.sort, page: page - 1, size: this.size}; 38 | if(otherQueryParams) { 39 | _.merge(parameters, otherQueryParams); 40 | } 41 | this.base.getList(parameters).then(function(objects) { 42 | self.afterObjectsGet(objects, userData); 43 | }); 44 | }; 45 | return PaginationLoader; 46 | }); -------------------------------------------------------------------------------- /src/modules/shared/controllers/createOrUpdateModalController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | /** 4 | * Basic controller for the modal that is used for editing and creating entities. 5 | * 6 | * Expects a "child"-controller name that will be instantiated after the basic setup of the scope. The child controller will get the scope of this 7 | * controller injected and thus can override any methods. It also gets the userdata injected. 8 | */ 9 | return ['$scope', '$uibModalInstance', 'controllerName', '$controller', 'templateName', 'createOrUpdateModal.userdata', 'titleTranslateCode', 10 | function($scope, $modalInstance, controllerName, $controller, templateName, userdata, titleTranslateCode) { 11 | $scope.errors = []; 12 | 13 | $scope.titleTranslateCode = titleTranslateCode; 14 | $scope.templateName = templateName; 15 | 16 | $scope.closeModal = function(resolve) { 17 | $modalInstance.close(resolve); 18 | }; 19 | 20 | $scope.cancel = function() { 21 | $modalInstance.dismiss(); 22 | }; 23 | 24 | //Instantiate the child controller and inject the current scope and the userdata into it. 25 | $controller(controllerName, { 26 | $scope: $scope, 27 | 'createOrUpdateModal.userdata': userdata 28 | }); 29 | }]; 30 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/autosize.js: -------------------------------------------------------------------------------- 1 | define(['jQuery'], function($) { 2 | 'use strict'; 3 | return [function() { 4 | return { 5 | restrict: 'A', 6 | compile: function(element) { 7 | 8 | /** 9 | * Create a mirror element to let the width be calculated by the browser 10 | * @type {*|jQuery|HTMLElement} 11 | */ 12 | var mirror = $(''); 13 | 14 | /** 15 | * Apply all relevant CSS styles 16 | */ 17 | ['fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'letterSpacing', 'textIndent', 'textTransform', 'wordSpacing'].forEach(function(style) { 18 | mirror[0].style[style] = element.css(style); 19 | }); 20 | $('body').append(mirror); 21 | 22 | return function(scope, element, attributes) { 23 | function updateWidth() { 24 | var value = element.val(); 25 | if(!value) { 26 | value = element.attr('placeholder') || ''; 27 | } 28 | if(value === mirror.text()) { 29 | return; 30 | } 31 | 32 | mirror.text(value); 33 | element.width(mirror.width() + 5); 34 | } 35 | 36 | scope.$watch(attributes.ngModel, function() { 37 | updateWidth(); 38 | }); 39 | 40 | element.bind('keydown keypress', function() { 41 | updateWidth(); 42 | }); 43 | }; 44 | } 45 | }; 46 | }]; 47 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/bsCheckbox.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['base.services.user', function(UserService) { 4 | return { 5 | restrict: 'E', 6 | templateUrl: 'src/modules/shared/partials/bsCheckbox.tpl.html', 7 | scope: {'checked': '=', 'change': '&', role: '@'}, 8 | controller: ['$scope', function($scope) { 9 | $scope.editable = !$scope.role || UserService.userHasAuthority($scope.role); 10 | }] 11 | }; 12 | }]; 13 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/bsText.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | /** 4 | * Bootstrap text input with validation error messages. 5 | *

    6 | * Attributes: 7 | * * propertyName: The name/path that the property will have in the error message object. E.g. 'address.street' or 'firstName' 8 | * * translateCode: code to lookup in angular-translate, e.g. 'ADDRESS.STREET' 9 | * * path: reference to the model property that this field represents, e.g. address.street. 10 | * * inline: if the label should have the class sr-only (inline) or form-label (!inline). If not provided: false 11 | * * errors: A reference to the array containing potential validation errors. If not provided, $scope.$parent.errors will be used. 12 | */ 13 | return [function() { 14 | return { 15 | restrict: 'E', 16 | templateUrl: 'src/modules/shared/partials/bsText.tpl.html', 17 | scope: {propertyName: '@', translateCode: '@', path: '=', inline: '@'}, 18 | controller: ['$scope', function($scope) { 19 | $scope.inline = $scope.inline || false; 20 | $scope.$parent.$watch('errors', function() { 21 | $scope.errors = $scope.$parent.errors; 22 | }); 23 | }] 24 | }; 25 | }]; 26 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/directives.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'modules/shared/directives/bsText', 3 | 'modules/shared/directives/bsEdit', 4 | 'modules/shared/directives/bsCheckbox', 5 | 'modules/shared/directives/autosize', 6 | 'modules/shared/directives/inlineDatepicker', 7 | 'modules/shared/directives/dateInterval', 8 | 'modules/shared/directives/errorDisplay', 9 | 'modules/shared/directives/tableSort', 10 | 'modules/shared/directives/pdfDownload' 11 | ], function(bsText, bsEdit, bsCheckbox, autosize, inlineDatepicker, dateInterval, errorDisplay, tableSort, pdfDownload) { 12 | 'use strict'; 13 | return { 14 | init: function(module) { 15 | module.directive('bsText', bsText); 16 | module.directive('bsEdit', bsEdit); 17 | module.directive('bsCheckbox', bsCheckbox); 18 | module.directive('autosize', autosize); 19 | module.directive('inlineDatepicker', inlineDatepicker); 20 | module.directive('dateInterval', dateInterval); 21 | module.directive('errorDisplay', errorDisplay); 22 | module.directive('tableSort', tableSort); 23 | module.directive('pdfDownload', pdfDownload); 24 | } 25 | }; 26 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/errorDisplay.js: -------------------------------------------------------------------------------- 1 | define(['angular'], function(angular) { 2 | 'use strict'; 3 | /** 4 | * This directive is used on form-group elements. It watches the scope.errors array and adds the has-error class to the form group element 5 | * if an error for the given property is found. The property is passed by name into the directive. It also adds an help-block 6 | * span element that contains the error text. 7 | * 8 | * Usage: 9 | * 10 | */ 11 | return ['$compile', function($compile) { 12 | return { 13 | restrict: 'A', 14 | link: function(scope, element, attrs) { 15 | var el = angular.element('{{errorText(\'' + attrs.errorDisplay + '\')}}'); 16 | 17 | scope.hasError = function(property, errors) { 18 | for (var i = 0; i < errors.length; i++) { 19 | if (errors[i].property === property) { 20 | return true; 21 | } 22 | } 23 | return false; 24 | }; 25 | 26 | scope.errorText = function(property) { 27 | if (scope.hasError(property, scope.errors)) { 28 | for (var i = 0; i < scope.errors.length; i++) { 29 | if (scope.errors[i].property === property) { 30 | return scope.errors[i].message; 31 | } 32 | } 33 | } else { 34 | return ''; 35 | } 36 | }; 37 | 38 | scope.$watch('errors', function(newErrors) { 39 | if (scope.hasError(attrs.errorDisplay, newErrors)) { 40 | element.addClass('has-error'); 41 | } else { 42 | element.removeClass('has-error'); 43 | } 44 | }); 45 | 46 | $compile(el)(scope); 47 | element.append(el); 48 | } 49 | }; 50 | }]; 51 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/inlineDatepicker.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | /** 4 | * Represents an inline datepicker (i.e. a text field with a button to open the datepicker. 5 | * Attributes: 6 | * * model: The model that should be bound to the datepicker. 7 | */ 8 | return [function() { 9 | return { 10 | restrict: 'E', 11 | transclude: true, 12 | templateUrl: 'src/modules/shared/partials/inlineDatepicker.tpl.html', 13 | scope: { model: '=' }, 14 | replace: true, 15 | controller: ['$scope', function($scope) { 16 | $scope.opened = false; 17 | $scope.openDate = function($event) { 18 | $event.stopPropagation(); 19 | $event.preventDefault(); 20 | $scope.opened = true; 21 | }; 22 | $scope.dateOptions = { 23 | 'year-format': '\'yyyy\'', 24 | 'starting-day': 1 25 | }; 26 | }] 27 | }; 28 | }]; 29 | }); -------------------------------------------------------------------------------- /src/modules/shared/directives/tableSort.js: -------------------------------------------------------------------------------- 1 | /* jshint camelcase: false */ 2 | define([], function() { 3 | 'use strict'; 4 | /** 5 | * Directive that appends an down- and up-arrow to the element it's applied to. When one clicks one of the arrows a callback is called 6 | * with the string given as the directive value as the first argument and 1 for the down arrow (desc), 1 for the up arrow (asc). 7 | * 8 | * The callback can optionally be defined by "sort-callback", otherwise the directive expects a function called "sortBy" in the scope. 9 | */ 10 | return [function() { 11 | function link(scope, element, attrs) { 12 | scope._ts_sortBy = function(prop, dir) { 13 | if(attrs.sortCallback) { 14 | scope[attrs.sortCallback](prop, dir); 15 | } else { 16 | scope.sortBy(prop, dir); 17 | } 18 | }; 19 | } 20 | return { 21 | restrict: 'A', 22 | compile: function(element, attrs) { 23 | element.append( 24 | '' + 25 | '' 26 | ); 27 | return link; 28 | } 29 | }; 30 | }]; 31 | }); -------------------------------------------------------------------------------- /src/modules/shared/partials/bsCheckbox.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | {{checked}} -------------------------------------------------------------------------------- /src/modules/shared/partials/bsEdit.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 | - 3 | {{entity[propertyName]}} 4 |
    5 |
    6 |
    7 |
    8 |
    9 | 10 |
    11 |
    12 |
    13 |
    -------------------------------------------------------------------------------- /src/modules/shared/partials/bsText.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 |
    6 |
    7 | -------------------------------------------------------------------------------- /src/modules/shared/partials/createOrUpdateModal.tpl.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/modules/shared/partials/dateInterval.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | 14 | 15 | 18 | 19 |
    20 |
    21 | 22 |
    23 | 24 |
    25 | 34 | 35 | 38 | 39 |
    40 |
    41 |
    -------------------------------------------------------------------------------- /src/modules/shared/partials/inlineDatepicker.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 10 | 11 | 12 | 13 |
    -------------------------------------------------------------------------------- /src/modules/shared/partials/pdfDownload.tpl.html: -------------------------------------------------------------------------------- 1 | {{ 'ACTIONS.DOWNLOAD' | translate }} -------------------------------------------------------------------------------- /src/modules/shared/shared.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'modules/shared/directives/directives', 'modules/shared/controllers/createOrUpdateModalController', 2 | 'modules/shared/services/createOrUpdateModalService'], 3 | function(angular, directives, createOrUpdateModalController, createOrUpdateModalService) { 4 | 'use strict'; 5 | var configFn = []; 6 | var shared = angular.module('shared', configFn); 7 | directives.init(shared); 8 | shared.controller('shared.controllers.create-or-update-modal',createOrUpdateModalController); 9 | shared.service('shared.services.create-or-update-modal', createOrUpdateModalService); 10 | return shared; 11 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/administration.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 |

    5 | 6 |
    7 |
    8 |

    9 |

    10 | 11 |
    12 |
    13 |

    14 |

    15 | 16 |
    17 |
    -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/contactPersons/newOrEdit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 |
    -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/contactPersons/newOrEditController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return ['$scope', 'createOrUpdateModal.userdata', 'Restangular', function($scope, userdata, Restangular) { 4 | var isNew = true, companyHref, controller = this; 5 | 6 | /** 7 | * First check if we're editing a contact person or creating a new one. If the userdata contains a contact person we're editing. 8 | */ 9 | if(userdata.contactPerson) { 10 | isNew = false; 11 | $scope.contactPerson = _.clone(userdata.contactPerson, true); 12 | } else { 13 | $scope.contactPerson = {}; 14 | companyHref = userdata.companyHref; 15 | } 16 | 17 | /** 18 | * Put the errors of a failed HTTP request into the scope. 19 | * @param response The HTTP response. 20 | */ 21 | controller.onFail = function(response) { 22 | $scope.errors = response.data.errors; 23 | }; 24 | 25 | /** 26 | * Create or update the contact person. 27 | */ 28 | $scope.saveEntity = function() { 29 | if(isNew) { 30 | $scope.contactPerson.company = companyHref; 31 | Restangular.all('contactPersons').post($scope.contactPerson).then(function(contactPerson) { 32 | $scope.closeModal(contactPerson); 33 | }, controller.onFail); 34 | } else { 35 | Restangular.one('contactPersons', $scope.contactPerson.id).patch($scope.contactPerson).then(function() { 36 | $scope.closeModal($scope.contactPerson); 37 | }, controller.onFail); 38 | } 39 | }; 40 | }]; 41 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/editController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return ['createOrUpdateModal.userdata', '$scope', 'Restangular', '$filter', function(company, $scope, Restangular, $filter) { 4 | var controller = this; 5 | 6 | //Since this is the original object from the display controller and we don't want changes in the edit form to be propagated 7 | //to the display (e.g. if the user presses cancel) we clone the object. 8 | $scope.company = _.clone(company, false); 9 | $scope.address = _.clone(company.address, false); 10 | 11 | controller.saveCompanyError = function(response) { 12 | if (response.status === 409) { 13 | $scope.errors = [ 14 | { 15 | entity: 'company', 16 | message: $filter('translate')('COMPANY.COMPANY_ID_CONFLICT'), 17 | property: 'companyId' 18 | } 19 | ]; 20 | } else { 21 | $scope.errors = response.data.errors; 22 | } 23 | throw new Error('Error saving company.'); 24 | }; 25 | 26 | $scope.saveEntity = function() { 27 | var company = _.pick($scope.company, ['id', 'version', 'companyId', 'name']); 28 | Restangular.one('companies', company.id).patch(company) 29 | .then(function() { 30 | var address = $scope.address; 31 | return Restangular.one('addresses', address.id).patch(address); 32 | }, controller.saveCompanyError) 33 | .then(function() { 34 | $scope.company.address = $scope.address; 35 | $scope.closeModal($scope.company); 36 | }) 37 | .catch(controller.saveCompanyError); 38 | }; 39 | }]; 40 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/list.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 |
    6 |
    7 | 8 | 9 | 12 |
    13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 |
    18 | {{company.name}} 19 |
    23 | 24 |
    25 |
    26 |
    27 | 28 |
    29 |
    30 |
    31 |
    32 | 33 |
    34 | 35 |
    36 | 37 |
    38 |
    39 | -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/listController.js: -------------------------------------------------------------------------------- 1 | define(['modules/shared/PaginationLoader'], function(PaginationLoader) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$state', 'shared.services.create-or-update-modal', function($scope, Restangular, $state, createOrUpdateModalService) { 4 | var paginationLoader = new PaginationLoader(Restangular.all('companies'), 'companies', 'name,asc', $scope, 5); 5 | 6 | $scope.setPage = function() { 7 | paginationLoader.loadPage($scope.companies.page.number); 8 | }; 9 | 10 | //initially load all companies 11 | paginationLoader.loadPage(); 12 | 13 | $scope.addNew = function() { 14 | var modalInstance = createOrUpdateModalService 15 | .showModal('trackr.administration.controllers.companies.new', 'src/modules/trackr/administration/companies/newOrEdit.tpl.html', 'COMPANY.CREATE_NEW'); 16 | modalInstance.result.then(function(company) { 17 | paginationLoader.loadPage(); 18 | $state.go('app.trackr.administration.companies.edit', {id: company.companyId}); 19 | }); 20 | return modalInstance; 21 | }; 22 | }]; 23 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/newController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$filter', function($scope, Restangular, $filter) { 4 | var controller = this; 5 | 6 | $scope.company = {}; 7 | $scope.address = {}; 8 | 9 | controller.onFail = function(response) { 10 | if (response.status === 409) { 11 | $scope.errors = [ 12 | { 13 | entity: 'company', 14 | message: $filter('translate')('COMPANY.COMPANY_ID_CONFLICT'), 15 | property: 'companyId' 16 | } 17 | ]; 18 | } else { 19 | $scope.errors = response.data.errors; 20 | } 21 | }; 22 | 23 | $scope.saveEntity = function() { 24 | // TODO: address exists now even if the company does not! 25 | Restangular.all('addresses') 26 | .post($scope.address) 27 | .then(function(address) { 28 | $scope.company.address = address._links.self.href; 29 | return Restangular.all('companies').post($scope.company); 30 | }) 31 | .then(function(company) { 32 | return $scope.closeModal(company); 33 | }) 34 | .catch(controller.onFail); 35 | }; 36 | }]; 37 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/companies/newOrEdit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    -------------------------------------------------------------------------------- /src/modules/trackr/administration/controllers.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'modules/trackr/administration/companies/listController', 3 | 'modules/trackr/administration/companies/displayController', 4 | 'modules/trackr/administration/companies/editController', 5 | 'modules/trackr/administration/companies/newController', 6 | 'modules/trackr/administration/companies/contactPersons/newOrEditController', 7 | 'modules/trackr/administration/projects/listController', 8 | 'modules/trackr/administration/projects/editController', 9 | 'modules/trackr/administration/projects/displayController', 10 | 'modules/trackr/administration/projects/newController' 11 | ], function(CompaniesListController, CompaniesDisplayController, CompaniesEditController, CompaniesNewController, 12 | ContactPersonsNewOrEditController, 13 | ProjectsListController, ProjectsEditController, ProjectsDisplayController, ProjectsNewController) { 14 | 'use strict'; 15 | return { 16 | init: function(module) { 17 | module.controller('trackr.administration.controllers.companies.list', CompaniesListController); 18 | module.controller('trackr.administration.controllers.companies.display', CompaniesDisplayController); 19 | module.controller('trackr.administration.controllers.companies.edit', CompaniesEditController); 20 | module.controller('trackr.administration.controllers.companies.new', CompaniesNewController); 21 | module.controller('trackr.administration.controllers.companies.contactPersons.new-or-edit', ContactPersonsNewOrEditController); 22 | module.controller('trackr.administration.controllers.projects.list', ProjectsListController); 23 | module.controller('trackr.administration.controllers.projects.edit', ProjectsEditController); 24 | module.controller('trackr.administration.controllers.projects.display', ProjectsDisplayController); 25 | module.controller('trackr.administration.controllers.projects.new', ProjectsNewController); 26 | } 27 | }; 28 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/addressEdit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 |
    -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/addressEditController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | function addressEditController(employee, $scope, Restangular) { 4 | /*jshint validthis:true */ 5 | var controller = this; 6 | controller.address = _.clone(employee.address); 7 | 8 | $scope.saveEntity = function() { 9 | var address = _.pick(controller.address, ['id', 'version', 'street', 'houseNumber', 'zipCode', 'city', 'country']); 10 | if(address.id === undefined) { 11 | createNewAddress(address); 12 | } else { 13 | updateAddress(address); 14 | } 15 | }; 16 | 17 | function createNewAddress(address) { 18 | Restangular.all('addresses').post(address) 19 | .then(function(savedAddress) { 20 | setAddressOnEmployee(savedAddress) 21 | .then(function() { 22 | $scope.closeModal(savedAddress); 23 | }); 24 | }, onFailedRequest); 25 | } 26 | 27 | function setAddressOnEmployee(address) { 28 | return Restangular.one('employees', employee.id) 29 | .customOperation('put', 'address', {}, {'Content-Type': 'text/uri-list'}, address._links.self.href); 30 | } 31 | 32 | function updateAddress(address) { 33 | var request = Restangular.one('addresses', address.id); 34 | _.assign(request, address); 35 | request.put().then(function(updatedAddress) { 36 | $scope.closeModal(updatedAddress); 37 | }, onFailedRequest); 38 | } 39 | 40 | function onFailedRequest(response) { 41 | $scope.errors = response.data.errors; 42 | } 43 | } 44 | 45 | addressEditController.$inject = ['createOrUpdateModal.userdata', '$scope', 'Restangular']; 46 | return addressEditController; 47 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/adminEmployeeModule.js: -------------------------------------------------------------------------------- 1 | define(['angular', './editController', './listController', './newController', './displayController', './addressEditController'], 2 | function(angular, EditController, ListController, NewController, DisplayController, AddressEditController) { 3 | 'use strict'; 4 | function config($stateProvider) { 5 | $stateProvider 6 | .state('app.trackr.administration.employees.edit', { 7 | url: '/{id:[\\d]+}', 8 | views: { 9 | 'employee': { 10 | templateUrl: 'src/modules/trackr/administration/employees/display.tpl.html', 11 | controller: 'trackr.administration.employees.displayController' 12 | } 13 | }, 14 | needsAuthority: 'ROLE_SUPERVISOR' 15 | }) 16 | .state('app.trackr.administration.employees', { 17 | url: '/employees', 18 | breadcrumbTranslateCode: 'EMPLOYEE.EMPLOYEES', 19 | views: { 20 | 'center@app': { 21 | templateUrl: 'src/modules/trackr/administration/employees/list.tpl.html', 22 | controller: 'trackr.administration.employees.listController' 23 | } 24 | }, 25 | needsAuthority: 'ROLE_SUPERVISOR' 26 | }); 27 | } 28 | 29 | config.$inject = ['$stateProvider']; 30 | var module = angular.module('trackr.administration.employees', [], config); 31 | 32 | module.controller('trackr.administration.employees.displayController', DisplayController); 33 | module.controller('trackr.administration.employees.editController', EditController); 34 | module.controller('trackr.administration.employees.addressEditController', AddressEditController); 35 | module.controller('trackr.administration.employees.newController', NewController); 36 | module.controller('trackr.administration.employees.listController', ListController); 37 | return module; 38 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/list.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 |
    6 |
    7 | 8 | 9 | 12 |
    13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 |
    18 | {{employee.firstName}} {{employee.lastName}} 19 |
    23 | 24 |
    25 |
    26 |
    27 | 29 |
    30 |
    31 |
    32 | 33 |
    34 | 35 |
    36 | 37 |
    38 | 39 |
    40 |
    -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/listController.js: -------------------------------------------------------------------------------- 1 | define(['modules/shared/PaginationLoader'], function (PaginationLoader) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$state', 'shared.services.create-or-update-modal', function($scope, Restangular, $state, createOrUpdateModalService) { 4 | var paginationLoader = new PaginationLoader(Restangular.all('employees'), 'employees', 'lastName', $scope, 10); 5 | 6 | $scope.setPage = function() { 7 | paginationLoader.loadPage($scope.employees.page.number); 8 | }; 9 | 10 | //initially load all employees 11 | paginationLoader.loadPage(); 12 | 13 | $scope.addNew = function() { 14 | var $modalInstance = createOrUpdateModalService 15 | .showModal('trackr.administration.employees.newController as ctrl', 16 | 'src/modules/trackr/administration/employees/newOrEdit.tpl.html', 17 | 'EMPLOYEE.CREATE_NEW' 18 | ); 19 | 20 | $modalInstance.result.then(function(employee) { 21 | paginationLoader.loadPage(); 22 | $state.go('app.trackr.administration.employees.edit', {id: employee.id}); 23 | }); 24 | }; 25 | 26 | $scope.$on('employee.deleted', function (event, id) { 27 | $scope.employees 28 | .filter(function (employee) { 29 | return employee.id === id; 30 | }) 31 | .forEach(function (employee) { 32 | employee.deleted = true; 33 | }); 34 | }); 35 | }]; 36 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/employees/newController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$filter', function($scope, Restangular, $filter) { 4 | var controller = this; 5 | 6 | $scope.employee = {}; 7 | 8 | controller.onFail = function(response) { 9 | if (response.status === 409) { 10 | $scope.errors = [{ 11 | entity: 'employee', 12 | message: $filter('translate')('CREDENTIAL.EMAIL_CONFLICT'), 13 | property: 'email' 14 | }]; 15 | } else { 16 | $scope.errors = response.data.errors; 17 | } 18 | }; 19 | 20 | $scope.saveEntity = function() { 21 | //In case of an error we remember the federal state so we can set it back. 22 | var federalState = $scope.employee.federalState; 23 | if($scope.employee.federalState) { 24 | $scope.employee.federalState = $scope.employee.federalState.name; 25 | } 26 | Restangular.all('employees').post($scope.employee) 27 | .then(function(employee) { 28 | $scope.closeModal(employee); 29 | }, function(response) { 30 | controller.onFail(response); 31 | $scope.employee.federalState = federalState; 32 | }); 33 | }; 34 | 35 | Restangular.one('federalStates').get().then(function(states) { 36 | $scope.states = states; 37 | }); 38 | 39 | $scope.openDate = function($event, name) { 40 | $event.stopPropagation(); 41 | $event.preventDefault(); 42 | controller[name] = true; 43 | }; 44 | }]; 45 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/display.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 |
    4 | 5 |

    {{project.name}} 6 | 9 |

    10 | 11 |
    12 |
    13 |
    {{project.identifier}}
    14 |
    15 |
    {{project.volume}}
    16 |
    17 |
    {{ project.hourlyRate | currency:'€' }}
    18 |
    19 |
    {{ project.dailyRate | currency:'€' }}
    20 |
    21 |
    {{ project.fixedPrice | currency:'€' }}
    22 |
    23 |
    {{ project.company.name + ' (' + project.company.companyId + ')'}}
    24 |
    25 |
    {{ project.debitor.name + ' (' + project.debitor.companyId + ')' }}
    26 |
    27 |
    28 | 29 |
    30 | -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/displayController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return ['$stateParams', '$scope', 'Restangular', '$state', 'shared.services.create-or-update-modal', 4 | function($stateParams, $scope, Restangular, $state, createOrUpdateModalService) { 5 | var controller = this; 6 | /* 7 | Initialization of $scope objects 8 | */ 9 | Restangular.oneUrl('projects', 'api/projects/search/findByIdentifier').get({ 10 | identifier: $stateParams.id, 11 | projection: 'withCompanyAndDebitor' 12 | }).then(function(project) { 13 | $scope.project = project; 14 | }); 15 | 16 | $scope.showEditForm = function() { 17 | var $modalInstance = createOrUpdateModalService 18 | .showModal('trackr.administration.controllers.projects.edit', 19 | 'src/modules/trackr/administration/projects/newOrEdit.tpl.html', 20 | 'ACTIONS.EDIT', $scope.project); 21 | $modalInstance.result.then(function(project) { 22 | var oldProjectId = $scope.project.identifier; 23 | $scope.project = project; 24 | if (oldProjectId !== project.identifier) { 25 | controller.projectIdentifierChanged(); 26 | } 27 | }); 28 | }; 29 | 30 | controller.projectIdentifierChanged = function() { 31 | //Change the company id in the list in the parent controller 32 | var projectInList = _.find($scope.$parent.projects, {id: $scope.project.id}); 33 | projectInList.identifier = $scope.project.identifier; 34 | //reload so the url is correct 35 | $state.go('app.trackr.administration.projects.edit', {id: $scope.project.identifier}); 36 | }; 37 | }]; 38 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/list.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 | 6 |
    7 |
    8 | 9 | 10 | 13 |
    14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
    19 | {{project.name}} 20 |
    24 | 25 |
    26 |
    27 |
    28 | 29 |
    30 |
    31 |
    32 | 33 |
    34 | 35 |
    36 | 37 | 38 | 39 |
    40 | 41 |
    42 | 43 |
    44 | -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/listController.js: -------------------------------------------------------------------------------- 1 | define(['modules/shared/PaginationLoader'], function(PaginationLoader) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', 'shared.services.create-or-update-modal', '$state', function($scope, Restangular, createOrUpdateModalService, $state) { 4 | var paginationLoader = new PaginationLoader(Restangular.all('projects'), 'projects', 'name,asc', $scope, 10); 5 | 6 | $scope.setPage = function() { 7 | paginationLoader.loadPage($scope.projects.page.number); 8 | }; 9 | 10 | //initially load all companies 11 | paginationLoader.loadPage(); 12 | 13 | $scope.addNew = function() { 14 | var $modalInstance = createOrUpdateModalService 15 | .showModal('trackr.administration.controllers.projects.new', 16 | 'src/modules/trackr/administration/projects/newOrEdit.tpl.html', 17 | 'PROJECT.CREATE_NEW'); 18 | $modalInstance.result.then(function(project) { 19 | paginationLoader.loadPage(); 20 | $state.go('app.trackr.administration.projects.edit', {id: project.identifier}); 21 | }); 22 | }; 23 | }]; 24 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/newController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function (_) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$filter', function($scope, Restangular, $filter) { 4 | var controller = this; 5 | $scope.project = {}; 6 | 7 | controller.onFail = function(response) { 8 | if(response.status === 409) { 9 | $scope.errors = [{ 10 | entity: 'project', 11 | message: $filter('translate')('PROJECT.IDENTIFIER_CONFLICT'), 12 | property: 'identifier' 13 | }]; 14 | } else { 15 | $scope.errors = response.data.errors; 16 | } 17 | }; 18 | 19 | controller.saveEntity = function(project) { 20 | var projectEntity = _.clone(project, false); 21 | 22 | if(project.company) { 23 | projectEntity.company = project.company._links.self.href; 24 | } 25 | if(project.debitor) { 26 | projectEntity.debitor = project.debitor._links.self.href; 27 | } 28 | 29 | Restangular.all('projects').post(projectEntity).then(function(response) { 30 | $scope.closeModal(response); 31 | }, controller.onFail); 32 | }; 33 | 34 | $scope.saveEntity = function() { 35 | controller.saveEntity($scope.project); 36 | }; 37 | 38 | $scope.getCompanies = function(searchString) { 39 | return Restangular.allUrl('companies', 'api/companies/search/findByNameLikeIgnoreCaseOrderByNameAsc') 40 | .getList({name: '%' + searchString + '%'}); 41 | }; 42 | }]; 43 | }); -------------------------------------------------------------------------------- /src/modules/trackr/administration/projects/newOrEdit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
    10 | 11 |
    12 | 17 |
    18 |
    19 | 20 |
    21 | 22 |
    23 | 28 |
    29 |
    30 |
    -------------------------------------------------------------------------------- /src/modules/trackr/directives/commentSection.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | /** 4 | * Comment section, currently only used for travel expense reports. 5 | * Comments are expected to have the fields submissionDate, employee and text. 6 | *

    7 | * Attributes: 8 | * * comments: {Array} A reference to the comments array 9 | * * preprocessor: {Function} A function that is called on a new comment before it is POSTed. Users can add new fields to it. Not optional! 10 | * * url: {String} A string that is the entity Restangular should use to POST, e.g. 'travelExpenseReportComments' 11 | */ 12 | return [function() { 13 | return { 14 | restrict: 'E', 15 | templateUrl: 'src/modules/trackr/directives/commentSection.tpl.html', 16 | scope: {comments: '=', preprocessor: '=', url: '@'}, 17 | controller: ['$scope', 'Restangular', 'trackr.services.employee', function($scope, Restangular, EmployeeService) { 18 | var controller = this; 19 | 20 | $scope.comment = {}; 21 | $scope.errors = {}; 22 | 23 | controller.addComment = function(comment, employee, url) { 24 | comment.employee = employee._links.self.href; 25 | comment.submissionDate = new Date(); 26 | return Restangular.all(url).post(comment); 27 | }; 28 | 29 | $scope.addComment = function() { 30 | var _comment = $scope.preprocessor($scope.comment); 31 | controller.addComment(_comment, EmployeeService.getEmployee(), $scope.url) 32 | .then(function(comment) { 33 | // The REST service does not return the employee but it is needed for display. 34 | comment.employee = EmployeeService.getEmployee(); 35 | $scope.comments.push(comment); 36 | $scope.comment = {}; 37 | }); 38 | }; 39 | }] 40 | }; 41 | }]; 42 | }); -------------------------------------------------------------------------------- /src/modules/trackr/directives/commentSection.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 |
      5 |
    • 6 |
      7 |
      {{comment.text}}
      8 |
      {{ comment.employee.firstName + ' ' + comment.employee.lastName }} ({{ comment.submissionDate | date:'dd.MM.yyyy HH:mm:ss' }})
      9 |
      10 |
    • 11 |
    12 |
    13 |
    14 |
    15 | 16 |
    17 |
    18 |
    -------------------------------------------------------------------------------- /src/modules/trackr/directives/directives.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'modules/trackr/directives/commentSection' 3 | ], function(commentSection) { 4 | 'use strict'; 5 | return { 6 | init: function(module) { 7 | module.directive('commentSection', commentSection); 8 | } 9 | }; 10 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/address_book/addressBookController.js: -------------------------------------------------------------------------------- 1 | define(['modules/shared/PaginationLoader'], function(PaginationLoader) { 2 | 'use strict'; 3 | return ['Restangular', '$scope', function(Restangular, $scope) { 4 | var paginationLoader = new PaginationLoader(Restangular.all('address_book'), 'employees', 'lastName,asc', $scope, 10); 5 | 6 | $scope.setPage = function() { 7 | paginationLoader.loadPage($scope.employees.page.number); 8 | }; 9 | 10 | $scope.sortBy = function(property, direction) { 11 | var directionText = 'desc'; 12 | if(direction == -1) { 13 | directionText = 'asc'; 14 | } 15 | paginationLoader.loadPage($scope.employees.page.number, null, null, property + ',' + directionText); 16 | }; 17 | 18 | //initially load all companies 19 | paginationLoader.loadPage(); 20 | }]; 21 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/address_book/address_book.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 | 4 | 7 | 10 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 |
    5 | 6 | 8 | 9 | 11 | 12 | 14 | 15 | 17 | 18 |
    {{employee.firstName}}{{employee.lastName}}{{employee.title}}{{employee.phoneNumber}}{{employee.email}} 28 | {{employee.address.street}} {{employee.address.houseNumber}}
    29 | {{employee.address.zipCode}} {{employee.address.city}}
    30 | {{employee.address.country}} 31 |
    34 | 35 | -------------------------------------------------------------------------------- /src/modules/trackr/employee/controllers.js: -------------------------------------------------------------------------------- 1 | define( 2 | [ 3 | './selfController', 4 | './selfEditController', 5 | './timesheet/timesheetController', 6 | './timesheet/timesheetOverviewController', 7 | './vacation/listController', 8 | './vacation/newController', 9 | './address_book/addressBookController', 10 | './sick_days/sickDaysController', 11 | './sick_days/sickDaysEditController' 12 | ], 13 | function(SelfController, SelfEditController, TimeSheetController, TimesheetOverviewController, VacationListController, 14 | VacationNewController, AddressBookController, SickDaysController, SickDaysEditController) { 15 | 'use strict'; 16 | return { 17 | init: function(module) { 18 | module.controller('trackr.employee.controllers.self', SelfController); 19 | module.controller('trackr.employee.controllers.self-edit', SelfEditController); 20 | module.controller('trackr.employee.controllers.timesheet', TimeSheetController); 21 | module.controller('trackr.employee.controllers.timesheet-overview', TimesheetOverviewController); 22 | module.controller('trackr.employee.controllers.vacation-list', VacationListController); 23 | module.controller('trackr.employee.controllers.vacation-new', VacationNewController); 24 | module.controller('trackr.employee.controllers.address_book', AddressBookController); 25 | module.controller('trackr.employee.controllers.sick-days', SickDaysController); 26 | module.controller('trackr.employee.controllers.sick-days-edit', SickDaysEditController); 27 | } 28 | }; 29 | } 30 | ); -------------------------------------------------------------------------------- /src/modules/trackr/employee/employee.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 | 3 |
    4 |
    5 |

    6 | 7 |
    8 |
    9 |

    10 | 11 |
    12 |
    13 |

    14 | 15 |
    16 |
    17 |

    18 | 19 |
    20 |
    21 |
    22 |
    23 |

    24 | 25 |
    26 |
    27 |

    28 | 29 |
    30 |
    31 |

    32 | 33 |
    34 |
    35 | -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/edit.tpl.html: -------------------------------------------------------------------------------- 1 |

    #{{report.id}} 2 | 3 |

    4 | 5 |

    {{ 'TRAVEL_EXPENSE_REPORT.' + report.status | translate }} 6 | ({{report.approver.firstName + ' ' + report.approver.lastName}}) 7 |

    8 |
    {{ report.debitor.name }} - {{report.project.name}}
    9 | 10 |
    11 | 12 |
    13 | 14 |
    15 |
    16 | 17 | 18 |
    19 |
    20 |
    21 | 22 |
    23 |
    24 | 25 | 26 |
    27 | -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expenseEditController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | function expenseEditController(userdata, TravelExpenseService, $scope, Restangular, $filter) { 4 | /*jshint validthis:true */ 5 | var controller = this; 6 | $scope.expense = _.clone(userdata.expense, false); 7 | $scope.expenseTypes = TravelExpenseService.getTypes(); 8 | 9 | controller.onFail = function(response) { 10 | $scope.errors = response.data.errors; 11 | }; 12 | 13 | controller.saveExpense = function(expense) { 14 | var expenseEntity = _.pick(expense, ['id', 'version', 'type', 'fromDate', 'toDate', 'vat', 'cost', 'comment', 'paid']); 15 | Restangular.one('travelExpenses', expenseEntity.id).patch(expenseEntity) 16 | .then(function(result) { 17 | $scope.closeModal(result); 18 | }, controller.onFail); 19 | }; 20 | 21 | $scope.saveEntity = function() { 22 | controller.saveExpense($scope.expense); 23 | }; 24 | 25 | /** 26 | * Translate the travel expense type from its enum value. 27 | * @param type The enum value (e.g. TAXI) 28 | * @returns {*} The translation (e.g. Taxi) 29 | */ 30 | $scope.translateTravelExpenseType = function(type) { 31 | return $filter('translate')('TRAVEL_EXPENSE.TYPE_VALUES.' + type); 32 | }; 33 | 34 | $scope.openDate = function($event, name) { 35 | $event.stopPropagation(); 36 | $event.preventDefault(); 37 | controller[name] = !controller[name]; 38 | }; 39 | 40 | $scope.dateOptions = { 41 | 'year-format': '\'yyyy\'', 42 | 'starting-day': 1 43 | }; 44 | } 45 | expenseEditController.$inject = ['createOrUpdateModal.userdata', 'trackr.services.travelExpense', '$scope', 'Restangular', '$filter']; 46 | return expenseEditController; 47 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expenseNewController.js: -------------------------------------------------------------------------------- 1 | define([], function () { 2 | 'use strict'; 3 | 4 | var expenseNewController = function($scope, Restangular, $filter, TravelExpenseService) { 5 | var controller = this; 6 | 7 | $scope.ctrl = this; // todo legacy because the included modal references it this way.. 8 | $scope.expense = {}; 9 | $scope.expenseTypes = TravelExpenseService.getTypes(); 10 | 11 | $scope.addNewExpense = function(report) { 12 | $scope.expense.report = report._links.self.href; 13 | $scope.expense.submissionDate = new Date(); 14 | Restangular.all('travelExpenses').post($scope.expense).then(function(expense) { 15 | $scope.errors = []; 16 | $scope.expense = { 17 | toDate: expense.toDate, 18 | fromDate: expense.fromDate 19 | }; 20 | $scope.$emit('newExpense', expense); 21 | }, function(response) { 22 | $scope.errors = response.data.errors; 23 | }); 24 | }; 25 | 26 | /** 27 | * Translate the travel expense type from its enum value. 28 | * @param type The enum value (e.g. TAXI) 29 | * @returns {*} The translation (e.g. Taxi) 30 | */ 31 | $scope.translateTravelExpenseType = function(type) { 32 | return $filter('translate')('TRAVEL_EXPENSE.TYPE_VALUES.' + type); 33 | }; 34 | 35 | $scope.openDate = function($event, name) { 36 | $event.stopPropagation(); 37 | $event.preventDefault(); 38 | controller[name] = !controller[name]; 39 | }; 40 | 41 | $scope.dateOptions = { 42 | 'year-format': '\'yyyy\'', 43 | 'starting-day': 1 44 | }; 45 | 46 | }; 47 | expenseNewController.$inject = ['$scope', 'Restangular', '$filter', 'trackr.services.travelExpense']; 48 | return expenseNewController; 49 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expensesDecorator.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | 4 | /** 5 | * Add up the cost of all expenses. 6 | * @returns {Number} 7 | */ 8 | function totalCost() { 9 | /*jshint validthis:true */ 10 | return this.reduce(function(prev, expense) { 11 | return prev + parseFloat(expense.cost); 12 | }, 0); 13 | } 14 | 15 | /** 16 | * Add up the cost of all expenses that are not paid yet. 17 | * @returns {Number} 18 | */ 19 | function totalReimbursement() { 20 | /*jshint validthis:true */ 21 | return this.reduce(function(prev, expense) { 22 | return prev + (expense.paid ? 0 : parseFloat(expense.cost)); 23 | }, 0); 24 | } 25 | 26 | /** 27 | * Decorate an expenses array with two methods to calculate the sum of costs and not-paid costs. 28 | */ 29 | return function(expenses) { 30 | expenses.totalCost = totalCost; 31 | expenses.totalReimbursement = totalReimbursement; 32 | }; 33 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expensesTable.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
    {{ expense.fromDate | date:'dd.MM.yyyy'}}{{ expense.toDate | date:'dd.MM.yyyy'}}{{ expense.vat }} %{{ expense.cost | currency:'€'}}{{ expense.comment }} 21 | 24 | 27 |
    : {{expenses.totalCost() | currency:'€'}}: {{expenses.totalReimbursement() | currency:'€'}}
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expensesTableController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | function expensesTableController($scope, Restangular, ConfirmationDialogService, createOrUpdateModalService) { 4 | $scope.expenses = []; 5 | 6 | /** 7 | * Remove an expense from the report. 8 | * @param expense The expense to remove. 9 | */ 10 | $scope.removeExpense = function(expense) { 11 | function deleteExpense() { 12 | Restangular.one('travelExpenses', expense.id).remove().then(function() { 13 | _.remove($scope.expenses, {id: expense.id}); 14 | }); 15 | } 16 | 17 | ConfirmationDialogService.openConfirmationDialog('ACTIONS.REALLY_DELETE').result.then(deleteExpense); 18 | }; 19 | 20 | /** 21 | * Opens the edit form for a single expense and updates the list if the user edited the expense. 22 | * @param expense 23 | */ 24 | $scope.showEditForm = function(expense) { 25 | var $modalInstance = createOrUpdateModalService 26 | .showModal('trackr.employee.expenses.expenseEditController as ctrl', 27 | 'src/modules/trackr/employee/expenses/expense-edit.tpl.html', 28 | 'ACTIONS.EDIT', { 29 | expense: expense 30 | }); 31 | 32 | $modalInstance.result.then(function(editedExpense) { 33 | var index = _.findIndex($scope.expenses, {id: editedExpense.id }); 34 | $scope.expenses[index] = editedExpense; 35 | }); 36 | }; 37 | 38 | } 39 | 40 | expensesTableController.$inject = ['$scope', 'Restangular', 'base.services.confirmation-dialog', 'shared.services.create-or-update-modal']; 41 | return expensesTableController; 42 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/expensesTableDirective.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | var expensesTableDirective = function() { 4 | return { 5 | restrict: 'E', 6 | scope: { 7 | expenses: '=', 8 | editable: '=' 9 | }, 10 | templateUrl: 'src/modules/trackr/employee/expenses/expensesTable.tpl.html', 11 | controller: 'trackr.employee.expenses.expenseTableController' 12 | }; 13 | }; 14 | return expensesTableDirective; 15 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/report-new.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 9 |
    10 |
    11 | 17 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/expenses/reportNewController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', 'trackr.services.employee', function($scope, Restangular, EmployeeService) { 4 | var controller = this; 5 | $scope.report = { 6 | employee: EmployeeService.getEmployeeHref(), 7 | status: 'PENDING' 8 | }; 9 | $scope.errors = {}; 10 | 11 | $scope.getCompanies = function(searchString) { 12 | return Restangular.allUrl('companies', 'api/companies/search/findByNameLikeIgnoreCaseOrderByNameAsc') 13 | .getList({name: '%' + searchString + '%'}); 14 | }; 15 | 16 | $scope.loadProjects = function(company) { 17 | controller.project = undefined; 18 | company.all('projects').getList().then(function(projects) { 19 | $scope.projects = projects; 20 | }); 21 | }; 22 | 23 | $scope.saveEntity = function() { 24 | if(controller.company) { 25 | $scope.report.debitor = controller.company._links.self.href; 26 | } 27 | if(controller.project) { 28 | $scope.report.project = controller.project._links.self.href; 29 | } 30 | Restangular.all('travelExpenseReports').post($scope.report).then(function(report) { 31 | $scope.closeModal(report); 32 | }, function(response) { 33 | $scope.errors = response.data.errors; 34 | }); 35 | }; 36 | }]; 37 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/self-edit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/self.tpl.html: -------------------------------------------------------------------------------- 1 |

    {{employee.firstName + ' ' + employee.lastName}} 2 | 5 |

    6 | 7 |
    8 |
    9 |
    {{employee.title}}
    10 |
    11 |
    {{ employee.salary | currency:'€' }}
    12 |
    13 |
    {{employee.phoneNumber}}
    14 |
    15 |
    {{employee.address.street}} {{employee.address.houseNumber}}
    16 |
    17 |
    {{employee.address.zipCode}} {{employee.address.city}}
    18 |
    19 |
    {{employee.address.country}}
    20 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/selfController.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | function selfController($scope, UserService, Restangular, createOrUpdateModalService) { 4 | var user = UserService.getUser(); 5 | Restangular.one('employees', user.id).one('self').get() 6 | .then(function(employee) { 7 | $scope.employee = employee; 8 | }); 9 | 10 | $scope.showEditForm = function() { 11 | var $modalInstance = createOrUpdateModalService 12 | .showModal('trackr.employee.controllers.self-edit', 13 | 'src/modules/trackr/employee/self-edit.tpl.html', 14 | 'ACTIONS.EDIT', $scope.employee); 15 | $modalInstance.result.then(function(result) { 16 | $scope.employee = result; 17 | }); 18 | }; 19 | 20 | } 21 | selfController.$inject = ['$scope', 'base.services.user', 'Restangular', 'shared.services.create-or-update-modal']; 22 | return selfController; 23 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/selfEditController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return ['createOrUpdateModal.userdata', '$scope', 'Restangular', function(employee, $scope, Restangular) { 4 | var controller = this; 5 | $scope.employee = _.clone(employee, true); 6 | 7 | controller.saveEntity = function(employee) { 8 | var request = Restangular.one('employees', employee.id).one('self'); 9 | _.assign(request, _.pick(employee, ['version', 'phoneNumber', 'firstName', 'lastName', 'address'])); 10 | request.put() 11 | .then(function(updatedEmployee) { 12 | $scope.closeModal(updatedEmployee); 13 | }, function(response) { 14 | $scope.errors = response.data.errors; 15 | }); 16 | }; 17 | 18 | $scope.saveEntity = function() { 19 | controller.saveEntity($scope.employee); 20 | }; 21 | }]; 22 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/sick_days/sick-days-edit.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | 13 | 14 | 17 | 18 |
    19 |
    20 | 21 |
    22 | 23 |
    24 | 32 | 33 | 36 | 37 |
    38 |
    39 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/sick_days/sick-days.tpl.html: -------------------------------------------------------------------------------- 1 |

    2 |
    3 |
    4 | 5 |
    6 |
    7 |
    8 | 9 |
    10 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 |
    19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 35 | 36 |
    {{sickDay.startDate | date:'dd.MM.yyyy' }}{{sickDay.endDate | date:'dd.MM.yyyy' }}{{totalDays(sickDay)}} 31 | 34 |
    37 |
    38 |
    39 | -------------------------------------------------------------------------------- /src/modules/trackr/employee/sick_days/sickDaysEditController.js: -------------------------------------------------------------------------------- 1 | define(['lodash', 'moment'], function(_, moment) { 2 | 'use strict'; 3 | return ['$scope', 'createOrUpdateModal.userdata', 'Restangular', function($scope, sickDays, Restangular) { 4 | var controller = this; 5 | $scope.sickDays = _.pick(sickDays, 'id', 'version', 'startDate', 'endDate'); 6 | 7 | $scope.saveEntity = function() { 8 | controller.saveSickDays($scope.sickDays); 9 | }; 10 | 11 | $scope.openDate = function(event, name) { 12 | event.stopPropagation(); 13 | event.preventDefault(); 14 | $scope[name] = !$scope[name]; 15 | }; 16 | 17 | controller.onError = function(response) { 18 | $scope.errors = response.data.errors; 19 | }; 20 | 21 | controller.saveSickDays = function(sickDays) { 22 | if(sickDays.startDate) { 23 | sickDays.startDate = moment(sickDays.startDate).format('YYYY-MM-DD'); 24 | } 25 | if(sickDays.endDate) { 26 | sickDays.endDate = moment(sickDays.endDate).format('YYYY-MM-DD'); 27 | } 28 | Restangular.one('sickDays', sickDays.id) 29 | .patch(sickDays) 30 | .then(function(savedSickDays) { 31 | $scope.closeModal(savedSickDays); 32 | }, controller.onError); 33 | }; 34 | 35 | $scope.dateOptions = { 36 | 'year-format': '\'yyyy\'', 37 | 'starting-day': 1 38 | }; 39 | }]; 40 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/vacation/list.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 |
    5 | 6 |

    7 | : {{employee.vacationEntitlement}} 8 |

    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 39 | 40 | 41 |
    {{vacationRequest.startDate | date:'dd.MM.yyyy' }}{{vacationRequest.endDate | date:'dd.MM.yyyy' }}{{vacationRequest.numberOfDays}} 28 | 29 | {{vacationRequest.approver.firstName + ' ' + vacationRequest.approver.lastName}} 30 | 31 | 32 | {{ vacationRequest.approvalDate | date:'dd.MM.yyyy HH:mm:ss' }} 35 | 36 | 37 | 38 |
    42 | 43 |
    44 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/vacation/listController.js: -------------------------------------------------------------------------------- 1 | define(['lodash'], function(_) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', 'trackr.services.employee', 'base.services.confirmation-dialog', 4 | function($scope, Restangular, EmployeeService, ConfirmationDialogService) { 5 | $scope.employee = EmployeeService.getEmployee(); 6 | 7 | Restangular.allUrl('vacationRequests', 'api/vacationRequests/search/findByEmployeeOrderByStartDateAsc') 8 | .getList({ 9 | employee: '/employees/' + $scope.employee.id, 10 | projection: 'withEmployeeAndApprover' 11 | }).then(function(vacationRequests) { 12 | $scope.vacationRequests = vacationRequests; 13 | }); 14 | 15 | /* 16 | This will be fired by the vacation-new controller. 17 | */ 18 | $scope.$on('newVacationRequest', function(event, data) { 19 | $scope.vacationRequests.push(data); 20 | }); 21 | 22 | $scope.cancelVacationRequest = function(vacationRequest) { 23 | function deleteVacationRequest() { 24 | vacationRequest.remove().then(function() { 25 | _.remove($scope.vacationRequests, {id: vacationRequest.id}); 26 | }); 27 | } 28 | 29 | ConfirmationDialogService.openConfirmationDialog('ACTIONS.REALLY_DELETE').result.then(deleteVacationRequest); 30 | }; 31 | }]; 32 | }); -------------------------------------------------------------------------------- /src/modules/trackr/employee/vacation/new.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |
    5 | 6 |
    7 |
    8 |
    9 |
    10 | 11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 |
    19 |
    -------------------------------------------------------------------------------- /src/modules/trackr/employee/vacation/newController.js: -------------------------------------------------------------------------------- 1 | define(['moment'], function(moment) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', 'trackr.services.employee', function($scope, Restangular, EmployeeService) { 4 | var controller = this; 5 | $scope.vacationRequest = {}; 6 | $scope.errors = []; 7 | 8 | controller.emitSavedVacationRequest = function(vacationRequest) { 9 | vacationRequest.startDate = moment(vacationRequest.startDate).format('YYYY-MM-DD'); 10 | vacationRequest.endDate = moment(vacationRequest.endDate).format('YYYY-MM-DD'); 11 | $scope.$emit('newVacationRequest', vacationRequest); 12 | }; 13 | 14 | $scope.submitVacationRequest = function(vacationRequest) { 15 | vacationRequest.employee = EmployeeService.getEmployeeHref(); 16 | vacationRequest.startDate = moment(vacationRequest.startDate).format('YYYY-MM-DD'); 17 | vacationRequest.endDate = moment(vacationRequest.endDate).format('YYYY-MM-DD'); 18 | Restangular.all('vacationRequests').post(vacationRequest).then(function(response) { 19 | controller.emitSavedVacationRequest(response); 20 | $scope.errors = []; 21 | }, function(response) { 22 | $scope.errors = response.data.errors; 23 | }); 24 | }; 25 | }]; 26 | }); -------------------------------------------------------------------------------- /src/modules/trackr/services/employeeService.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['base.services.user', 'Restangular', function(UserService, Restangular) { 4 | var employee; 5 | return { 6 | loadEmployee: function () { 7 | return Restangular.one('employees', UserService.getUser().id).get().then(function(_employee) { 8 | employee = _employee; 9 | return employee; 10 | }); 11 | }, 12 | getEmployee: function() { 13 | return employee; 14 | }, 15 | getEmployeeHref: function() { 16 | return employee._links.self.href; 17 | } 18 | }; 19 | }]; 20 | }); -------------------------------------------------------------------------------- /src/modules/trackr/services/services.js: -------------------------------------------------------------------------------- 1 | define(['modules/trackr/services/employeeService', 2 | 'modules/trackr/services/travelExpenseReportService', 3 | 'modules/trackr/services/travelExpenseService'], function(EmployeeService, TravelExpenseReportService, TravelExpenseService) { 4 | 'use strict'; 5 | return { 6 | init: function(module) { 7 | module.service('trackr.services.employee', EmployeeService); 8 | module.service('trackr.services.travelExpenseReport', TravelExpenseReportService); 9 | module.service('trackr.services.travelExpense', TravelExpenseService); 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /src/modules/trackr/services/travelExpenseReportService.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$http', 'Restangular', function($http, Restangular) { 4 | return { 5 | approve: function(travelExpenseReport) { 6 | return $http.put('api/travelExpenseReports/' + travelExpenseReport.id + '/approve'); 7 | }, 8 | 9 | reject: function(travelExpenseReport) { 10 | return $http.put('api/travelExpenseReports/' + travelExpenseReport.id + '/reject'); 11 | }, 12 | 13 | submit: function(travelExpenseReport) { 14 | return $http.put('api/travelExpenseReports/' + travelExpenseReport.id + '/submit'); 15 | }, 16 | 17 | /** 18 | * Return all travel expense reports with their employee loaded. 19 | * @param status The status to search for (SUBMITTED, PENDING, REJECTED, APPROVED) 20 | */ 21 | findByStatusWithEmployee: function(status) { 22 | return Restangular.allUrl('travelExpenseReports', 'api/travelExpenseReports/search/findByStatusOrderByEmployee_LastNameAsc').getList({ 23 | status: status, 24 | projection: 'overview' 25 | }).then(function(reports) { 26 | return reports; 27 | }); 28 | } 29 | }; 30 | }]; 31 | }); -------------------------------------------------------------------------------- /src/modules/trackr/services/travelExpenseService.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return ['$http', function($http) { 4 | var expenseTypes; 5 | return { 6 | loadTypes: function() { 7 | return $http.get('api/travelExpenses/types').then(function(response) { 8 | expenseTypes = response.data; 9 | return expenseTypes; 10 | }); 11 | }, 12 | getTypes: function() { 13 | return expenseTypes; 14 | } 15 | }; 16 | }]; 17 | }); -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/billReportController.js: -------------------------------------------------------------------------------- 1 | define(['modules/trackr/supervisor/timeIntervalSetup'], function(timeIntervalSetup) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$filter', '$http', function($scope, Restangular, $filter, $http) { 4 | $scope.getCompanies = function(searchString) { 5 | return Restangular.allUrl('companies', 'api/companies/search/findByNameLikeIgnoreCaseOrderByNameAsc') 6 | .getList({name: '%' + searchString + '%'}); 7 | }; 8 | 9 | $scope.loadProjects = function(company) { 10 | company.all('projects').getList({projection: 'withCompanyAndDebitor'}).then(function(projects) { 11 | $scope.projects = projects; 12 | }); 13 | }; 14 | 15 | $scope.loadProjectData = function() { 16 | $scope.loadBillData(); 17 | }; 18 | 19 | $scope.loadBillData = function() { 20 | //only if the user has selected a project 21 | if($scope.project) { 22 | $http.get('api/billableTimes/findEmployeeMappingByProjectAndDateBetween', { 23 | params: { 24 | project: $scope.project.id, 25 | start: $filter('date')($scope.start, 'yyyy-MM-dd'), 26 | end: $filter('date')($scope.end, 'yyyy-MM-dd') 27 | } 28 | }).then(function(result) { 29 | $scope.employeeMapping = result.data; 30 | }); 31 | } 32 | }; 33 | 34 | timeIntervalSetup($scope, $scope.loadBillData); 35 | }]; 36 | }); -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/controllers.js: -------------------------------------------------------------------------------- 1 | define( 2 | [ 3 | './fileBillableHoursController', 4 | './fileBillableHoursSaveController', 5 | './billReportController', 6 | './vacation/vacationController' 7 | ], 8 | function(FileBillableHoursController, FileBillableHoursSaveController, BillReportController, 9 | VacationController) { 10 | 'use strict'; 11 | return { 12 | init: function(module) { 13 | module.controller('trackr.supervisor.controllers.file-billable-hours', FileBillableHoursController); 14 | module.controller('trackr.supervisor.controllers.save-billable-hours', FileBillableHoursSaveController); 15 | module.controller('trackr.supervisor.controllers.bill-report', BillReportController); 16 | module.controller('trackr.supervisor.controllers.vacation', VacationController); 17 | } 18 | }; 19 | } 20 | ); -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/expenses/supervisorExpensesModule.js: -------------------------------------------------------------------------------- 1 | define(['angular', './editController', './listController'], function(angular, EditController, ListController) { 2 | 'use strict'; 3 | function config($stateProvider) { 4 | $stateProvider 5 | .state('app.trackr.supervisor.expenses', { 6 | url: '/expenses', 7 | breadcrumbTranslateCode: 'PAGES.SUPERVISOR.TRAVEL_EXPENSE_REPORTS.TITLE', 8 | views: { 9 | 'center@app': { 10 | templateUrl: 'src/modules/trackr/supervisor/expenses/list.tpl.html', 11 | controller: 'trackr.supervisor.expenses.ListController' 12 | } 13 | } 14 | }) 15 | .state('app.trackr.supervisor.expenses.edit', { 16 | url: '/{id:\\d+}', 17 | breadcrumbTranslateCode: 'ACTIONS.EDIT', 18 | views: { 19 | 'center@app': { 20 | templateUrl: 'src/modules/trackr/supervisor/expenses/edit.tpl.html', 21 | controller: 'trackr.supervisor.expenses.EditController' 22 | } 23 | } 24 | }); 25 | } 26 | 27 | config.$inject = ['$stateProvider']; 28 | var module = angular.module('trackr.supervisor.expenses', [], config); 29 | module.controller('trackr.supervisor.expenses.ListController', ListController); 30 | module.controller('trackr.supervisor.expenses.EditController', EditController); 31 | return module; 32 | }); -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/fileBillableHoursController.js: -------------------------------------------------------------------------------- 1 | define(['modules/trackr/supervisor/timeIntervalSetup'], function(timeIntervalSetup) { 2 | 'use strict'; 3 | return ['$scope', 'Restangular', '$filter', '$http', function($scope, Restangular, $filter, $http) { 4 | $scope.loadWorktimes = function() { 5 | if($scope.project) { 6 | //Load this via http as it does not return standard items (custom DTOs without links) and Restangular would not be usefule. 7 | $http.get('api/workTimes/findEmployeeMappingByProjectAndDateBetween', { 8 | params: { 9 | project: $scope.project.id, 10 | start: $filter('date')($scope.start, 'yyyy-MM-dd'), 11 | end: $filter('date')($scope.end, 'yyyy-MM-dd') 12 | } 13 | }).then(function(response) { 14 | $scope.employeeMapping = response.data; 15 | }); 16 | } 17 | }; 18 | 19 | $scope.getProjectLabel = function(project) { 20 | if(project) { 21 | return project.name + ' (' + project.identifier + ')'; 22 | } else { 23 | return undefined; 24 | } 25 | }; 26 | 27 | $scope.getProjects = function(searchString) { 28 | var search = '%' + searchString + '%'; 29 | return Restangular.allUrl('projects', 'api/projects/search/findByNameLikeIgnoreCaseOrIdentifierLikeIgnoreCaseOrderByNameAsc').getList({name: search, identifier: search}); 30 | }; 31 | 32 | Restangular.all('projects').getList({sort: 'identifier,asc'}).then(function(projects) { 33 | $scope.projects = projects; 34 | }); 35 | 36 | timeIntervalSetup($scope, $scope.loadWorktimes); 37 | }]; 38 | }); -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/supervisor.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 |
    5 |
    6 |

    7 | 8 |
    9 |
    10 |

    11 | 12 |
    13 |
    14 |

    15 | 16 |
    17 |
    18 |
    19 |
    20 |

    21 | 22 |
    23 |
    -------------------------------------------------------------------------------- /src/modules/trackr/supervisor/timeIntervalSetup.js: -------------------------------------------------------------------------------- 1 | define(['moment'], function(moment) { 2 | 'use strict'; 3 | /** 4 | * Sets up a scope from an angular.js controller to have a start and end date. 5 | * 6 | * The start date will be the first day of the month, the end date the last. 7 | * 8 | * If one of the dates changes a callback will be called. 9 | * 10 | * Usage: Access the dates with "$scope.start" and "scope.end". 11 | */ 12 | return function($scope, callback) { 13 | $scope.start = moment().startOf('month').toDate(); 14 | $scope.end = moment().endOf('month').toDate(); 15 | 16 | $scope.$watch('start', callback); 17 | $scope.$watch('end', callback); 18 | }; 19 | }); -------------------------------------------------------------------------------- /src/modules/trackr/top-menu.tpl.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/trackr/trackr.js: -------------------------------------------------------------------------------- 1 | define(['angular', 2 | 'modules/trackr/services/services', 3 | 'modules/trackr/directives/directives', 4 | 'modules/trackr/administration/administrationModule', 5 | 'modules/trackr/employee/employeeModule', 6 | 'modules/trackr/supervisor/supervisorModule'], 7 | function(angular, services, directives) { 8 | 'use strict'; 9 | var configFn = ['trackr.administration', 'trackr.employee', 'trackr.supervisor']; 10 | var trackr = angular.module('trackr', configFn); 11 | 12 | trackr.config(['$stateProvider', function($stateProvider) { 13 | $stateProvider. 14 | state('app.trackr', { 15 | url: 'trackr', 16 | breadcrumbTranslateCode: 'PAGES.HOME.BREADCRUMB', 17 | resolve: { 18 | //We have to require the user injection here, otherwise on a full page refresh the 'user resolve' in tha 19 | //app state will be run *after* this employee resolve - which means the UserService does not know 20 | //the user yet and the EmployeeService will fail. 21 | employee: ['trackr.services.employee', 'app.user', function(EmployeeService) { 22 | return EmployeeService.loadEmployee(); 23 | }] 24 | }, 25 | views: { 26 | 'top-menu@app': { 27 | templateUrl: 'src/modules/trackr/top-menu.tpl.html', 28 | controller: 'base.controllers.navigation' 29 | }, 30 | 'breadcrumbs@app': { 31 | templateUrl: 'src/modules/base/partials/breadcrumbs.tpl.html', 32 | controller: 'base.controllers.breadcrumb-controller' 33 | }, 34 | 'center@app': { 35 | templateUrl: 'src/modules/trackr/welcome.tpl.html' 36 | } 37 | } 38 | }); 39 | }]); 40 | 41 | services.init(trackr); 42 | directives.init(trackr); 43 | return trackr; 44 | }); -------------------------------------------------------------------------------- /src/modules/trackr/welcome.tpl.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |

    4 | 5 | 6 |
    7 |
    8 | -------------------------------------------------------------------------------- /src/prodstarter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is only used in production to bootstrap the app. 3 | * 4 | * This file will be required in the minified file. It then requires 'app' thus executing all needed angular code before calling angular.bootstrap. 5 | */ 6 | define(['angular', 'app'], function(angular) { 7 | 'use strict'; 8 | angular.bootstrap(document, ['app']); 9 | }); -------------------------------------------------------------------------------- /test/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent": 4, 3 | "camelcase": true, 4 | "curly": true, 5 | "freeze": true, 6 | "latedef": true, 7 | "quotmark": "single", 8 | "unused": true, 9 | "undef": true, 10 | "strict": true, 11 | "trailing": true, 12 | "jquery": true, 13 | "node": true, 14 | "globals": { 15 | "define": true, 16 | "describe": true, 17 | "it": true, 18 | "beforeEach": true, 19 | "spyOn": true, 20 | "inject": true, 21 | "expect": true, 22 | "afterEach": true 23 | } 24 | } -------------------------------------------------------------------------------- /test/baseTestSetup.js: -------------------------------------------------------------------------------- 1 | define(['backendMock', 'angular-mocks', 'app'], function(backendMock) { 2 | 'use strict'; 3 | return function() { 4 | beforeEach(module('app')); 5 | beforeEach(inject(function($httpBackend, $injector) { 6 | backendMock($httpBackend); 7 | var UserService = $injector.get('base.services.user'); 8 | UserService.setUser({ 9 | id: 0, 10 | email: 'admin@techdev.de', 11 | enabled: true, 12 | authorities: [ 13 | {authority: 'ROLE_ADMIN', order: 0, id: 0} 14 | ] 15 | }); 16 | })); 17 | afterEach(inject(function($httpBackend) { 18 | $httpBackend.verifyNoOutstandingExpectation(); 19 | $httpBackend.verifyNoOutstandingRequest(); 20 | })); 21 | }; 22 | }); -------------------------------------------------------------------------------- /test/modules/base/controllers/navigationSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('base.controllers.navigation', function () { 4 | var NavigationController, scope, UserService; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller, $injector) { 8 | scope = $rootScope.$new(); 9 | UserService = $injector.get('base.services.user'); 10 | spyOn(UserService, 'getUser').andCallThrough(); 11 | NavigationController = $controller('base.controllers.navigation', { 12 | $scope: scope 13 | }); 14 | })); 15 | 16 | it('should put the active user into scope', inject(function($httpBackend) { 17 | $httpBackend.flush(); 18 | expect(UserService.getUser).toHaveBeenCalled(); 19 | expect(scope.user).toBeDefined(); 20 | })); 21 | 22 | it('changeLanguage should perform a put request on the API', inject(function($httpBackend) { 23 | scope.changeLanguage('en'); 24 | $httpBackend.expectPUT(/^api\/translations\?.*/); 25 | $httpBackend.flush(); 26 | })); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/modules/base/filters/configValueFilterSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('base.filters.configValueFilter', function () { 4 | baseTestSetup(); 5 | 6 | it('should parse a config key to a config value', inject(function(configValueFilter) { 7 | expect(configValueFilter('portalUrl')).toBeDefined(); 8 | })); 9 | 10 | it('should throw when a value does not exist', inject(function (configValueFilter) { 11 | expect( function() { configValueFilter('totally_not_existing_key'); }).toThrow('Configuration key totally_not_existing_key not found.'); 12 | })); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/modules/base/services/confirmationServiceMock.js: -------------------------------------------------------------------------------- 1 | define([], function() { 2 | 'use strict'; 3 | return { 4 | /** 5 | * Just executes the callback in the result. 6 | * @returns {{result: {then: then}}} 7 | */ 8 | openConfirmationDialog: function() { 9 | return { 10 | result: { 11 | then: function(cb) { 12 | cb(); 13 | } 14 | } 15 | }; 16 | } 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /test/modules/base/services/userSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('base.services.user', function() { 4 | var UserService; 5 | baseTestSetup(); 6 | beforeEach(inject(function($injector) { 7 | UserService = $injector.get('base.services.user'); 8 | })); 9 | 10 | it('setUser must define highestAuthority on the user object', function() { 11 | var user = UserService.getUser(); 12 | user.authorities = [{authority: 'ROLE_EMPLOYEE', order: 2, id: 2}, {authority: 'ROLE_SUPERVISOR', order: 1, id: 1}]; 13 | UserService.setUser(user); 14 | expect(user.highestAuthority).toBeDefined(); 15 | expect(user.highestAuthority.authority).toBe('ROLE_SUPERVISOR'); 16 | }); 17 | 18 | it('hasAuthority must return true for all if user is admin', function() { 19 | var hasAuthority = UserService.userHasAuthority('ROLE_ADMIN'); 20 | expect(hasAuthority).toBe(true); 21 | hasAuthority = UserService.userHasAuthority('ROLE_SUPERVISOR'); 22 | expect(hasAuthority).toBe(true); 23 | hasAuthority = UserService.userHasAuthority('ROLE_EMPLOYEE'); 24 | expect(hasAuthority).toBe(true); 25 | }); 26 | 27 | it('must return false for userHasAuthority if user has no authorities', function() { 28 | var user = UserService.getUser(); 29 | user.authorities = []; 30 | UserService.setUser(user); 31 | var hasAuthority = UserService.userHasAuthority('ROLE_EMPLOYEE'); 32 | expect(hasAuthority).toBe(false); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/modules/invoices/newControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('invoice new controller', function() { 4 | var NewController, scope; 5 | baseTestSetup(); 6 | 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | scope.closeModal = angular.noop; 10 | NewController = $controller('invoices.controllers.new', { 11 | $scope: scope 12 | }); 13 | })); 14 | 15 | it('must post to the server when calling addNew', inject(function($httpBackend) { 16 | scope.saveEntity(); 17 | $httpBackend.expectPOST('api/invoices'); 18 | $httpBackend.flush(); 19 | })); 20 | 21 | it('the error handler for posts must put a special text into the errors array if a conflict is returned', function() { 22 | var response = { status: 409 }; 23 | NewController.onFail(response); 24 | expect(scope.errors.length).toBeGreaterThan(0); 25 | expect(scope.errors[0].property).toBe('identifier'); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/modules/reportr/charts/colorsSpec.js: -------------------------------------------------------------------------------- 1 | define(['modules/reportr/charts/colors'], function(colors) { 2 | 'use strict'; 3 | describe('reports.charts.colors', function() { 4 | 5 | it('generates the requested amount of colors', function() { 6 | var generatedColors = colors.getColorWithHighlight(1); 7 | expect(generatedColors.length).toBe(1); 8 | }); 9 | 10 | it('generates both color and highlight', function() { 11 | var generatedColors = colors.getColorWithHighlight(1); 12 | expect(generatedColors[0].color).toBeDefined(); 13 | expect(generatedColors[0].highlight).toBeDefined(); 14 | }); 15 | 16 | it('generates strings that start with #', function() { 17 | var generatedColors = colors.getColorWithHighlight(1); 18 | expect(generatedColors[0].color[0]).toBe('#'); 19 | expect(generatedColors[0].highlight[0]).toBe('#'); 20 | }); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/modules/reportr/employeeHoursControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('reportr.controllers.employee-hours', function() { 4 | var EmployeeHoursController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | EmployeeHoursController = $controller('reportr.controllers.employee-hours', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | beforeEach(inject(function($httpBackend) { 15 | // Flush initial load of vacation requests 16 | $httpBackend.expectGET(/^api\/workTimes\/search\/findByDateBetween\?end=\d+&projection=withEmployee&start=\d+$/); 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('generate bar chart data', function() { 21 | var employeeHoursArray = [ ['employee1', 120], ['employee2', 130] ]; 22 | var barData = EmployeeHoursController.calculateBarChartData(employeeHoursArray); 23 | expect(barData.labels.length).toEqual(2); 24 | expect(barData.labels).toContain('employee1'); 25 | expect(barData.labels).toContain('employee2'); 26 | expect(barData.datasets[0].label).toBeDefined(); 27 | expect(barData.datasets[0].data.length).toBe(2); 28 | expect(barData.datasets[0].data).toContain(120); 29 | expect(barData.datasets[0].data).toContain(130); 30 | }); 31 | 32 | it('when a date is selected loadWorkTimes must be triggered', function() { 33 | spyOn(EmployeeHoursController, 'loadWorkTimes'); 34 | scope.dateSelected(new Date(), new Date()); 35 | expect(EmployeeHoursController.loadWorkTimes).toHaveBeenCalled(); 36 | }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /test/modules/reportr/lodashHelpersSpec.js: -------------------------------------------------------------------------------- 1 | define(['modules/reportr/lodashHelpers'], function(LodashHelpers) { 2 | 'use strict'; 3 | describe('LodashHelpers', function() { 4 | 5 | it('mapAndReduceValuesToSum with function key/number extractor', function() { 6 | var data = [ { prop1: 'one', prop2: 'two', number: 3}]; 7 | var mappers = { 8 | keyMapper: function(d) { 9 | return d.prop1 + d.prop2; 10 | }, 11 | numberMapper: function(d) { 12 | return d.number * 3; 13 | } 14 | }; 15 | spyOn(mappers, 'keyMapper').andCallThrough(); 16 | spyOn(mappers, 'numberMapper').andCallThrough(); 17 | 18 | var result = LodashHelpers.mapAndReduceValuesToSum(data, mappers.keyMapper, mappers.numberMapper); 19 | expect(mappers.keyMapper).toHaveBeenCalled(); 20 | expect(mappers.numberMapper).toHaveBeenCalled(); 21 | expect(result.length).toEqual(1); 22 | expect(result[0][0]).toEqual('onetwo'); 23 | expect(result[0][1]).toEqual(9); 24 | }); 25 | 26 | it('mapAndReduceValuesToSum with string key/number mapper', function() { 27 | var data = [ { prop1: 'one', number: 3}]; 28 | var result = LodashHelpers.mapAndReduceValuesToSum(data, 'prop1', 'number'); 29 | expect(result.length).toEqual(1); 30 | expect(result[0][0]).toEqual('one'); 31 | expect(result[0][1]).toEqual(3); 32 | }); 33 | }); 34 | }); -------------------------------------------------------------------------------- /test/modules/reportr/revenueControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('reportr.controllers.revenue', function() { 4 | var RevenueController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | RevenueController = $controller('reportr.controllers.revenue', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | beforeEach(inject(function($httpBackend) { 15 | // Flush initial load of invoices 16 | $httpBackend.expectGET(/^api\/invoices\/search\/findByCreationDateBetween\?end=\d+&projection=withDebitor&start=\d+/); 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('generate pie chart data', function() { 21 | var invoicesArray = [ ['project1', 400], ['project2', 500] ]; 22 | var pieData = RevenueController.generatePieData(invoicesArray); 23 | expect(pieData[0].label).toEqual('project1'); 24 | expect(pieData[0].value).toEqual('400.00'); 25 | expect(pieData[1].label).toEqual('project2'); 26 | expect(pieData[1].value).toEqual('500.00'); 27 | }); 28 | 29 | it('when a date is selected loadInvoices must be triggered', function() { 30 | spyOn(RevenueController, 'loadInvoices'); 31 | scope.dateSelected(new Date(), new Date()); 32 | expect(RevenueController.loadInvoices).toHaveBeenCalled(); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/modules/reportr/sortHelperSpec.js: -------------------------------------------------------------------------------- 1 | define(['modules/reportr/sortHelper'], function(SortHelper) { 2 | 'use strict'; 3 | describe('LodashHelpers', function() { 4 | 5 | it('sortArrayOfArrays with index as number', function() { 6 | var data = [ ['b', 1], ['a', 0] ]; 7 | SortHelper.sortArrayOfArrays(data, '0', -1); 8 | expect(data[0][0]).toEqual('a'); 9 | }); 10 | 11 | it('sortArrayOfArrays ascending index 0', function() { 12 | var data = [ ['b', 1], ['A', 0] ]; 13 | SortHelper.sortArrayOfArrays(data, 0, -1); 14 | expect(data[0][0]).toEqual('A'); 15 | }); 16 | 17 | it('sortArrayOfArrays descending index 0', function() { 18 | var data = [ ['A', 1], ['b', 0] ]; 19 | SortHelper.sortArrayOfArrays(data, 0, 1); 20 | expect(data[0][0]).toEqual('b'); 21 | }); 22 | 23 | it('sortArrayOfArrays descending index 1', function() { 24 | var data = [ ['A', 0], ['b', 1] ]; 25 | SortHelper.sortArrayOfArrays(data, 1, 1); 26 | expect(data[0][1]).toEqual(1); 27 | }); 28 | 29 | it('sortArrayOfArrays ascending index 1', function() { 30 | var data = [ ['A', 1], ['b', 0] ]; 31 | SortHelper.sortArrayOfArrays(data, 1, -1); 32 | expect(data[0][1]).toEqual(0); 33 | }); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/modules/reportr/travelExpenseControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('reportr.controllers.travel-expense', function() { 4 | var TravelExpenseController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | TravelExpenseController = $controller('reportr.controllers.travel-expense', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | beforeEach(inject(function($httpBackend) { 15 | // Flush initial load of vacation requests 16 | $httpBackend.expectGET(/^api\/travelExpenseReports\/search\/findBySubmissionDateBetween\?end=\d+&projection=withEmployeeAndExpenses&start=\d+$/); 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('generate bar chart data', function() { 21 | var travelExpenseArray = [ ['employee1', 1000], ['employee2', 900] ]; 22 | var barData = TravelExpenseController.calculateBarChartData(travelExpenseArray); 23 | expect(barData.labels.length).toEqual(2); 24 | expect(barData.labels).toContain('employee1'); 25 | expect(barData.labels).toContain('employee2'); 26 | expect(barData.datasets[0].label).toBeDefined(); 27 | expect(barData.datasets[0].data.length).toBe(2); 28 | expect(barData.datasets[0].data).toContain(1000); 29 | expect(barData.datasets[0].data).toContain(900); 30 | }); 31 | 32 | it('when a date is selected loadTravelExpenseReports must be triggered', function() { 33 | spyOn(TravelExpenseController, 'loadTravelExpenseReports'); 34 | scope.dateSelected(new Date(), new Date()); 35 | expect(TravelExpenseController.loadTravelExpenseReports).toHaveBeenCalled(); 36 | }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /test/modules/reportr/vacationControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('reportr.controllers.vacation', function() { 4 | var VacationController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | VacationController = $controller('reportr.controllers.vacation', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | beforeEach(inject(function($httpBackend) { 15 | // Flush initial load of vacation requests 16 | $httpBackend.expectGET(/^api\/vacationRequests\/daysPerEmployeeBetween\?end=\d+&projection=withEmployeeAndApprover&start=\d+$/); 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('generate bar chart data', function() { 21 | var invoicesArray = [ ['employee1', 3], ['employee2', 5] ]; 22 | var barData = VacationController.generateBarChartData(invoicesArray); 23 | expect(barData.labels.length).toEqual(2); 24 | expect(barData.labels).toContain('employee1'); 25 | expect(barData.labels).toContain('employee2'); 26 | expect(barData.datasets[0]).toBeDefined(); 27 | expect(barData.datasets[0].data.length).toBe(2); 28 | expect(barData.datasets[0].data).toContain(3); 29 | expect(barData.datasets[0].data).toContain(5); 30 | }); 31 | 32 | it('when a date is selected loadInvoices must be triggered', function() { 33 | spyOn(VacationController, 'loadVacationRequests'); 34 | scope.dateSelected(new Date(), new Date()); 35 | expect(VacationController.loadVacationRequests).toHaveBeenCalled(); 36 | }); 37 | }); 38 | }); -------------------------------------------------------------------------------- /test/modules/shared/PaginationLoaderSpec.js: -------------------------------------------------------------------------------- 1 | define(['modules/shared/PaginationLoader'], function(PaginationLoader) { 2 | 'use strict'; 3 | describe('shared.PaginationLoader', function() { 4 | var paginationLoader, restangularBase, scope; 5 | beforeEach(function() { 6 | restangularBase = { 7 | getList: function() {} 8 | }; 9 | spyOn(restangularBase, 'getList').andReturn({ 10 | then: function(callback) { callback([]); } 11 | }); 12 | scope = {}; 13 | paginationLoader = new PaginationLoader(restangularBase, 'name', 'sort', scope); 14 | }); 15 | 16 | it('Must have all properties attached', function() { 17 | expect(paginationLoader.base).toBeDefined(); 18 | expect(paginationLoader.name).toBe('name'); 19 | expect(paginationLoader.sort).toBe('sort'); 20 | expect(paginationLoader.$scope).toBeDefined(); 21 | expect(paginationLoader.size).toBe(5); 22 | }); 23 | 24 | it('Must call getList with page 0 if a new page is requested without parameter', function() { 25 | paginationLoader.loadPage(); 26 | expect(restangularBase.getList).toHaveBeenCalledWith({sort: 'sort', page: 0, size: 5}); 27 | }); 28 | 29 | it('Must call getList with the correct page parameter if a new page is requested with parameter', function() { 30 | paginationLoader.loadPage(5); 31 | expect(restangularBase.getList).toHaveBeenCalledWith({sort: 'sort', page: 4, size: 5}); 32 | }); 33 | 34 | it('Must put the returned objects into the scope after a page has been requested', function() { 35 | paginationLoader.loadPage(); 36 | expect(scope.name).toBeDefined(); 37 | }); 38 | }); 39 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/companies/editControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['angular', 'baseTestSetup', 'fixtures'], function(angular, baseTestSetup, fixtures) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.companies.edit', function() { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | scope.closeModal = angular.noop; 9 | 10 | var company = fixtures['api/companies']._embedded.companies[0]; 11 | company.address = fixtures['api/addresses']._embedded.addresses[0]; 12 | company.contactPersons = fixtures['api/contactPersons']._embedded.contactPersons; 13 | 14 | EditController = $controller('trackr.administration.controllers.companies.edit', { 15 | $scope: scope, 16 | 'createOrUpdateModal.userdata': company 17 | }); 18 | })); 19 | 20 | it('saveEntity must first save the company and then the address', inject(function($httpBackend) { 21 | scope.saveEntity(); 22 | $httpBackend.expectPATCH('api/companies/' + scope.company.id); 23 | $httpBackend.expectPATCH('api/addresses/' + scope.company.address.id); 24 | $httpBackend.flush(); 25 | })); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/companies/listControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.companies.list', function() { 4 | var ListController, scope, state; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | state = { 9 | go: angular.noop 10 | }; 11 | spyOn(state, 'go'); 12 | ListController = $controller('trackr.administration.controllers.companies.list', { 13 | $scope: scope, 14 | $state: state 15 | }); 16 | })); 17 | 18 | beforeEach(inject(function($httpBackend) { 19 | $httpBackend.flush(); 20 | })); 21 | 22 | it('must load companies at the start', function() { 23 | expect(scope.companies).toBeDefined(); 24 | expect(scope.companies.length).toBeGreaterThan(0); 25 | }); 26 | 27 | it('must open the modal dialog on addNew and reload the companies if the modal is closed and go to the new company', inject(function($httpBackend) { 28 | var modalInstance = scope.addNew(); 29 | expect(modalInstance).toBeDefined(); 30 | $httpBackend.flush(); 31 | modalInstance.close({id: 0}); 32 | $httpBackend.expectGET(/^\api\/companies\?.*$/); 33 | $httpBackend.flush(); 34 | expect(state.go).toHaveBeenCalled(); 35 | })); 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/companies/newControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.companies.new', function() { 4 | var NewController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | scope.closeModal = angular.noop; 9 | NewController = $controller('trackr.administration.controllers.companies.new', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | it('must have a new company object', function() { 15 | expect(scope.company).toBeDefined(); 16 | }); 17 | 18 | it('must save the company and address', inject(function($httpBackend) { 19 | scope.saveEntity(); 20 | $httpBackend.expectPOST('api/addresses'); 21 | $httpBackend.expectPOST('api/companies'); 22 | $httpBackend.flush(); 23 | })); 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/employees/addressEditControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.employees.addressEditController', function() { 4 | var AddressEditController, scope; 5 | 6 | baseTestSetup(); 7 | 8 | beforeEach(inject(function($rootScope, $controller) { 9 | scope = $rootScope.$new(); 10 | scope.closeModal = angular.noop; 11 | AddressEditController = $controller('trackr.administration.employees.addressEditController', { 12 | 'createOrUpdateModal.userdata': {id: 0}, 13 | '$scope': scope 14 | }); 15 | })); 16 | 17 | it('create a new address and updates the employee when the address is new.', inject(function($httpBackend) { 18 | AddressEditController.address = {street: 'foo'}; 19 | scope.saveEntity(); 20 | $httpBackend.expectPOST('api/addresses'); 21 | $httpBackend.expectPUT('api/employees/0/address'); 22 | $httpBackend.flush(); 23 | })); 24 | 25 | it('updates an existing address', inject(function($httpBackend) { 26 | AddressEditController.address = {id: 0, street: 'foo'}; 27 | scope.saveEntity(); 28 | $httpBackend.expectPUT('api/addresses/0'); 29 | $httpBackend.flush(); 30 | })); 31 | }); 32 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/employees/displayControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.administration.employees.displayController', function () { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | EditController = $controller('trackr.administration.employees.displayController', { 9 | $scope: scope, 10 | $stateParams: { 11 | id: 0 12 | } 13 | }); 14 | })); 15 | 16 | beforeEach(inject(function($httpBackend) { 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('must have an employee in scope', function() { 21 | expect(scope.employee).toBeDefined(); 22 | }); 23 | 24 | it('must have the federal states in scope', function() { 25 | expect(scope.states).toBeDefined(); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/employees/editControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.employees.editController', function () { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | scope.closeModal = angular.noop; 9 | EditController = $controller('trackr.administration.employees.editController', { 10 | $scope: scope, 11 | 'createOrUpdateModal.userdata': { 12 | employee: { 13 | id: 0, 14 | federalState: {name: 'BERLIN'} 15 | }, 16 | states: [] 17 | } 18 | }); 19 | })); 20 | 21 | it('must have an employee in scope', function() { 22 | expect(scope.employee).toBeDefined(); 23 | }); 24 | 25 | it('must have the federal states in scope', function() { 26 | expect(scope.states).toBeDefined(); 27 | }); 28 | 29 | it('must PATCH the employee when updating', inject(function($httpBackend) { 30 | scope.saveEntity(); 31 | $httpBackend.expectPATCH('api/employees/0'); 32 | $httpBackend.flush(); 33 | })); 34 | }); 35 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/employees/listControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.employees.listController', function() { 4 | var ListController, scope, state; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | state = { 9 | go: angular.noop 10 | }; 11 | spyOn(state, 'go'); 12 | ListController = $controller('trackr.administration.employees.listController', { 13 | $scope: scope, 14 | $state: state 15 | }); 16 | })); 17 | 18 | beforeEach(inject(function($httpBackend) { 19 | $httpBackend.flush(); 20 | })); 21 | 22 | it('must load companies at the start', function() { 23 | expect(scope.employees).toBeDefined(); 24 | expect(scope.employees.length).toBeGreaterThan(0); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/employees/newControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.employees.listController', function() { 4 | var NewController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | scope.closeModal = angular.noop; 9 | NewController = $controller('trackr.administration.employees.newController', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | it('must load the federal states on load', inject(function($httpBackend) { 15 | $httpBackend.expectGET('api/federalStates'); 16 | $httpBackend.flush(); 17 | expect(scope.states).toBeDefined(); 18 | })); 19 | 20 | it('must have a new employee object', inject(function($httpBackend) { 21 | $httpBackend.flush(); 22 | expect(scope.employee).toBeDefined(); 23 | })); 24 | 25 | it('must save the emplyoee and credential', inject(function($httpBackend) { 26 | scope.saveEntity(); 27 | $httpBackend.expectPOST('api/employees'); 28 | $httpBackend.flush(); 29 | })); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/projects/displayControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.projects.display', function () { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | EditController = $controller('trackr.administration.controllers.projects.display', { 9 | $scope: scope, 10 | $stateParams: { 11 | id: 0 12 | } 13 | }); 14 | })); 15 | 16 | beforeEach(inject(function($httpBackend) { 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('must have a project in the scope', function() { 21 | expect(scope.project).toBeDefined(); 22 | }); 23 | }); 24 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/projects/editControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.projects.edit', function () { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | EditController = $controller('trackr.administration.controllers.projects.edit', { 9 | $scope: scope, 10 | 'createOrUpdateModal.userdata': {} 11 | }); 12 | })); 13 | 14 | it('must have a project in the scope', function() { 15 | expect(scope.project).toBeDefined(); 16 | }); 17 | }); 18 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/projects/listControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.projects.list', function () { 4 | var ListController, scope, state; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | state = { 9 | go: angular.noop 10 | }; 11 | spyOn(state, 'go'); 12 | ListController = $controller('trackr.administration.controllers.projects.list', { 13 | $scope: scope, 14 | $state: state 15 | }); 16 | })); 17 | 18 | beforeEach(inject(function($httpBackend) { 19 | $httpBackend.flush(); 20 | })); 21 | 22 | it('must have projects in the scope', function() { 23 | expect(scope.projects).toBeDefined(); 24 | expect(scope.projects.length).toBeGreaterThan(0); 25 | }); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/modules/trackr/administration/projects/newControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.administration.controllers.projects.new', function() { 4 | var NewController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | scope.closeModal = angular.noop; 9 | NewController = $controller('trackr.administration.controllers.projects.new', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | it('must have a new company object', function() { 15 | expect(scope.project).toBeDefined(); 16 | }); 17 | 18 | it('Must save the project.', inject(function($httpBackend) { 19 | scope.saveEntity(); 20 | $httpBackend.expectPOST('api/projects'); 21 | $httpBackend.flush(); 22 | })); 23 | 24 | it('Must search for companies in the backend', inject(function($httpBackend) { 25 | scope.getCompanies('test'); 26 | $httpBackend.expectGET(/api\/companies\/search\/findByNameLikeIgnoreCaseOrderByNameAsc\?.*/); 27 | $httpBackend.flush(); 28 | })); 29 | }); 30 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/expenses/expenseEditControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.employee.expenses.expenseEditController', function() { 4 | var EditController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | EditController = $controller('trackr.employee.expenses.expenseEditController', { 9 | $scope: scope, 10 | 'createOrUpdateModal.userdata': { 11 | expense: {} 12 | }, 13 | 'trackr.services.travelExpense': { 14 | getTypes: function() { return ['TAXI']; } 15 | } 16 | }); 17 | })); 18 | 19 | it('Must put the expense in the scope', function() { 20 | expect(scope.expense).toBeDefined(); 21 | }); 22 | 23 | it('Must put the expense types in the scope', function() { 24 | expect(scope.expenseTypes).toBeDefined(); 25 | }); 26 | 27 | it('Must patch the expense when updating', inject(function($httpBackend) { 28 | scope.closeModal = angular.noop; 29 | scope.expense = {id: 0, type: 'TAXI'}; 30 | scope.saveEntity(); 31 | $httpBackend.expectPATCH('api/travelExpenses/0'); 32 | $httpBackend.flush(); 33 | })); 34 | 35 | it('puts the errors in the scope when the response with the failure method', function() { 36 | var response = {data: {errors: []}}; 37 | EditController.onFail(response); 38 | expect(scope.errors).toBeDefined(); 39 | expect(scope.errors.length).toBe(0); 40 | }); 41 | }); 42 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/expenses/expenseNewControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.employee.expenses.expenseNewController', function() { 4 | var NewController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($controller, $rootScope) { 7 | scope = $rootScope.$new(); 8 | NewController = $controller('trackr.employee.expenses.expenseNewController', { 9 | $scope: scope 10 | }); 11 | })); 12 | 13 | it('must POST a new expense to the server on addNewExpense, set the report and submissionDate property on it', inject(function($httpBackend) { 14 | scope.expense = {cost: 1}; 15 | var report = { 16 | _links: {self: {href: 'report/1'}} 17 | }; 18 | scope.addNewExpense(report); 19 | $httpBackend.expectPOST('api/travelExpenses'); 20 | expect(scope.expense.report).toBe('report/1'); 21 | expect(scope.expense.submissionDate).toBeDefined(); 22 | $httpBackend.flush(); 23 | })); 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/expenses/expensesDecoratorSpec.js: -------------------------------------------------------------------------------- 1 | define(['modules/trackr/employee/expenses/expensesDecorator'], function(expensesDecorator) { 2 | 'use strict'; 3 | describe('The expenses decorator', function() { 4 | 5 | it('must add a totalCost function to the array that calculates the sum of costs', function() { 6 | var expenses = [{cost: 10}, {cost: 13}]; 7 | expensesDecorator(expenses); 8 | expect(expenses.totalCost).toBeDefined(); 9 | expect(expenses.totalCost()).toBe(23); 10 | }); 11 | 12 | it('must add a totalReimbursement function to the array that calculates the sum of non-paid costs', function() { 13 | var expenses = [{cost: 10, paid: false}, {cost: 13, paid: true}]; 14 | expensesDecorator(expenses); 15 | expect(expenses.totalReimbursement).toBeDefined(); 16 | expect(expenses.totalReimbursement()).toBe(10); 17 | }); 18 | }); 19 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/expenses/expensesTableControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'confirmationServiceMock'], function(baseTestSetup, ConfirmationServiceMock) { 2 | 'use strict'; 3 | describe('trackr.employee.expenses.expenseTableController', function() { 4 | var expenseTableController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | expenseTableController = $controller('trackr.employee.expenses.expenseTableController', { 9 | $scope: scope, 10 | 'base.services.confirmation-dialog': ConfirmationServiceMock 11 | }); 12 | })); 13 | 14 | it('must call DELETE when removing an expense', inject(function($httpBackend) { 15 | scope.expenses = [{id: 0}, {id: 1}]; 16 | scope.removeExpense(scope.expenses[1]); 17 | $httpBackend.expectDELETE('api/travelExpenses/' + scope.expenses[1].id); 18 | $httpBackend.flush(); 19 | expect(scope.expenses.length).toBe(1); 20 | })); 21 | }); 22 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/expenses/listControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'fixtures', 'angular'], function(baseTestSetup, fixtures, angular) { 2 | 'use strict'; 3 | describe('trackr.employee.expenses.expenseReportListController', function() { 4 | var ListController, scope, state; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | var employee = { 9 | id: 0, 10 | _links: { self: {href: ''}} 11 | }; 12 | state = { 13 | go: angular.noop 14 | }; 15 | var reports = fixtures['api/travelExpenseReports']._embedded.travelExpenseReports; 16 | ListController = $controller('trackr.employee.expenses.expenseReportListController', { 17 | $scope: scope, 18 | reports: reports, 19 | $state: state, 20 | employee: employee 21 | }); 22 | })); 23 | 24 | beforeEach(inject(function($httpBackend) { 25 | $httpBackend.flush(); 26 | })); 27 | 28 | it('calculateReportTotal must sum up all costs.', function() { 29 | var report = {expenses: [{cost: 10}, {cost: 13}]}; 30 | var totalCost = ListController.calculateReportTotal(report); 31 | expect(totalCost).toBe(23); 32 | }); 33 | }); 34 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/selfControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.employee.controllers.self', function() { 4 | var UserService, SelfController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller, $injector) { 8 | scope = $rootScope.$new(); 9 | UserService = $injector.get('base.services.user'); 10 | spyOn(UserService, 'getUser').andReturn({ 11 | id: 1 12 | }); 13 | SelfController = $controller('trackr.employee.controllers.self', { 14 | $scope: scope, 15 | 'base.services.user': UserService 16 | }); 17 | })); 18 | 19 | it('should get the active user on start', inject(function($httpBackend) { 20 | $httpBackend.flush(); 21 | expect(UserService.getUser).toHaveBeenCalled(); 22 | })); 23 | 24 | it('should load the employee from via the API', inject(function($httpBackend) { 25 | $httpBackend.expectGET('api/employees/1/self'); 26 | $httpBackend.flush(); 27 | expect(scope.employee).toBeDefined(); 28 | })); 29 | 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/selfEditControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup', 'angular'], function(baseTestSetup, angular) { 2 | 'use strict'; 3 | describe('trackr.employee.controllers.self-edit', function() { 4 | var SelfEditController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | scope.closeModal = angular.noop; 10 | var userdata = {id: 0, firstName: 'firstName', lastName: 'lastName'}; 11 | SelfEditController = $controller('trackr.employee.controllers.self-edit', { 12 | $scope: scope, 13 | 'createOrUpdateModal.userdata': userdata 14 | }); 15 | })); 16 | 17 | it('must update the employee via PUT', inject(function($httpBackend) { 18 | scope.saveEntity(); 19 | $httpBackend.expectPUT('api/employees/0/self'); 20 | $httpBackend.flush(); 21 | })); 22 | }); 23 | 24 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/timesheet/timesheetOverviewControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.employee.controllers.timesheet-overview', function() { 4 | var TimesheetController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | TimesheetController = $controller('trackr.employee.controllers.timesheet-overview', { 9 | $scope: scope 10 | }); 11 | spyOn(TimesheetController, 'convertToGroupedWorktimes').andReturn({}); 12 | })); 13 | 14 | it('Must load the current worktimes on start', inject(function($httpBackend) { 15 | $httpBackend.flush(); 16 | expect(scope.workTimes).toBeDefined(); 17 | })); 18 | 19 | it('Must convert the worktimes to the grouped worktimes on start', inject(function($httpBackend) { 20 | $httpBackend.flush(); 21 | expect(scope.groupedWorkTimes).toBeDefined(); 22 | expect(TimesheetController.convertToGroupedWorktimes).toHaveBeenCalled(); 23 | })); 24 | 25 | it('totalHours must calculate the total hours with floating point', inject(function($httpBackend) { 26 | $httpBackend.flush(); 27 | var totalHours = TimesheetController.totalHours('17:30:00', '08:00:00'); 28 | expect(totalHours).toBe(9.5); 29 | })); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/modules/trackr/employee/vacation/newControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.employee.controllers.vacation-new', function() { 4 | var EmployeeService, VacationNewController, scope, vacationRequest; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | EmployeeService = { 9 | getEmployeeHref: function() { 10 | return ''; 11 | } 12 | }; 13 | VacationNewController = $controller('trackr.employee.controllers.vacation-new', { 14 | $scope: scope, 15 | 'trackr.services.employee': EmployeeService 16 | }); 17 | vacationRequest = { 18 | startDate: new Date(), 19 | endDate: new Date() 20 | }; 21 | })); 22 | 23 | it('It must have a vacation request blueprint', function() { 24 | expect(scope.vacationRequest).toBeDefined(); 25 | }); 26 | 27 | it('It must have an error array', function() { 28 | expect(scope.errors).toBeDefined(); 29 | }); 30 | 31 | it('Emit new vacation request must emit a new vacation request', function() { 32 | var emitted = false; 33 | scope.$on('newVacationRequest', function() { 34 | emitted = true; 35 | }); 36 | VacationNewController.emitSavedVacationRequest(vacationRequest); 37 | expect(emitted).toBe(true); 38 | }); 39 | 40 | it('Must load the vacation requests', inject(function($httpBackend) { 41 | scope.submitVacationRequest(vacationRequest); 42 | $httpBackend.expectPOST('api/vacationRequests'); 43 | $httpBackend.flush(); 44 | })); 45 | }); 46 | }); -------------------------------------------------------------------------------- /test/modules/trackr/services/employeeServiceSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.services.employee', function() { 4 | var EmployeeService; 5 | baseTestSetup(); 6 | beforeEach(inject(function($injector) { 7 | EmployeeService = $injector.get('trackr.services.employee'); 8 | })); 9 | 10 | it('loadEmployee must load the employee from the backend', inject(function($httpBackend) { 11 | EmployeeService.loadEmployee(); 12 | $httpBackend.expectGET(/^api\/employees\/\d+/); 13 | $httpBackend.flush(); 14 | })); 15 | 16 | it('getEmployee must return an employee after loadEmployee was called', inject(function($httpBackend) { 17 | EmployeeService.loadEmployee(); 18 | $httpBackend.flush(); 19 | var employee = EmployeeService.getEmployee(); 20 | expect(employee).toBeDefined(); 21 | })); 22 | 23 | it('getEmployeeHref must return a HREF after loadEmployee was called', inject(function($httpBackend) { 24 | EmployeeService.loadEmployee(); 25 | $httpBackend.flush(); 26 | var employeeHref = EmployeeService.getEmployeeHref(); 27 | expect(employeeHref).toBeDefined(); 28 | expect(employeeHref).not.toBe(''); 29 | })); 30 | }); 31 | }); -------------------------------------------------------------------------------- /test/modules/trackr/supervisor/billReportControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.supervisor.controllers.bill-report', function() { 4 | var BillReportController, scope; 5 | 6 | baseTestSetup(); 7 | beforeEach(inject(function($rootScope, $controller) { 8 | scope = $rootScope.$new(); 9 | BillReportController = $controller('trackr.supervisor.controllers.bill-report', { 10 | $scope: scope 11 | }); 12 | })); 13 | 14 | it('getCompanies must search for companies in the backend', inject(function($httpBackend) { 15 | scope.getCompanies('test'); 16 | $httpBackend.expectGET(/^api\/companies\/search\/findByNameLikeIgnoreCaseOrderByNameAsc\?.*/); 17 | $httpBackend.flush(); 18 | })); 19 | 20 | it('loadProjects must search for companies in the backend', inject(function($httpBackend) { 21 | var company; 22 | scope.getCompanies('test').then(function(companies) { 23 | company = companies[0]; 24 | }); 25 | $httpBackend.flush(); 26 | scope.loadProjects(company); 27 | $httpBackend.expectGET(/^api\/companies\/\d+\/projects/); 28 | $httpBackend.flush(); 29 | expect(scope.projects).toBeDefined(); 30 | })); 31 | 32 | it('loadProjectData must load BillData', inject(function($httpBackend) { 33 | scope.project = { 34 | _links: { debitor: { href: 'api/companies/0' } } 35 | }; 36 | scope.loadProjectData(); 37 | $httpBackend.expectGET(/^api\/billableTimes\/findEmployeeMappingByProjectAndDateBetween\?.*/); 38 | $httpBackend.flush(); 39 | })); 40 | }); 41 | }); -------------------------------------------------------------------------------- /test/modules/trackr/supervisor/expenses/listControllerSpec.js: -------------------------------------------------------------------------------- 1 | define(['baseTestSetup'], function(baseTestSetup) { 2 | 'use strict'; 3 | describe('trackr.supervisor.expenses.ListController', function() { 4 | var ListController, scope; 5 | baseTestSetup(); 6 | beforeEach(inject(function($rootScope, $controller) { 7 | scope = $rootScope.$new(); 8 | ListController = $controller('trackr.supervisor.expenses.ListController', { 9 | $scope: scope 10 | }); 11 | })); 12 | 13 | it('must put the reports in the scope', inject(function($httpBackend) { 14 | $httpBackend.flush(); 15 | expect(scope.reports.SUBMITTED).toBeDefined(); 16 | })); 17 | 18 | it('must put the approved reports in the scope', inject(function($httpBackend) { 19 | $httpBackend.flush(); 20 | expect(scope.reports.APPROVED).toBeDefined(); 21 | })); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/test-main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var tests = []; 3 | for (var file in window.__karma__.files) { 4 | if (window.__karma__.files.hasOwnProperty(file) && /spec\.js$/i.test(file) && ! /src\/vendor/i.test(file)) { 5 | tests.push(file); 6 | } 7 | } 8 | 9 | require.config({ 10 | baseUrl: '/base/src', 11 | paths: { 12 | 'jQuery': 'vendor/jquery/dist/jquery', 13 | 'twitter-bootstrap': 'vendor/bootstrap/dist/js/bootstrap', 14 | 'angular': 'vendor/angular/angular', 15 | 'angular-ui-router': 'vendor/angular-ui-router/release/angular-ui-router', 16 | 'angular-mocks': 'vendor/angular-mocks/angular-mocks', 17 | 'restangular':'vendor/restangular/dist/restangular', 18 | 'lodash': 'vendor/lodash/lodash', 19 | 'fixtures': '../test/fixtures', 20 | 'baseTestSetup': '../test/baseTestSetup', 21 | 'backendMock': '../test/backendMock', 22 | 'angular-translate': 'vendor/angular-translate/angular-translate', 23 | 'angular-translate-loader-url': 'vendor/angular-translate-loader-url/angular-translate-loader-url', 24 | 'angular-ui': 'vendor/angular-ui-bootstrap-bower/ui-bootstrap-tpls', 25 | 'moment': 'vendor/moment/moment', 26 | 'confirmationServiceMock': '../test/modules/base/services/confirmationServiceMock', 27 | 'chartjs': 'vendor/chartjs/Chart', 28 | 'randomColor': 'vendor/randomColor/randomColor', 29 | 'configuration': 'conf/trackr' 30 | }, 31 | shim: { 32 | 'angular': { exports: 'angular' }, 33 | 'angular-ui-router': { deps: ['angular']}, 34 | 'jQuery': { exports: '$' }, 35 | 'twitter-bootstrap': { deps: ['jQuery'] }, 36 | 'restangular': {deps: ['angular', 'lodash']}, 37 | 'angular-mocks': { deps: ['angular']}, 38 | 'angular-translate': { deps: ['angular'] }, 39 | 'angular-translate-loader-url': { deps: ['angular-translate'] }, 40 | 'angular-ui': { deps: ['angular'] }, 41 | 'angular-charts': { deps: ['d3', 'angular'] } 42 | }, 43 | deps: tests, 44 | 45 | callback: window.__karma__.start 46 | }); -------------------------------------------------------------------------------- /trackr.json: -------------------------------------------------------------------------------- 1 | {} --------------------------------------------------------------------------------