├── .bowerrc
├── .dockerignore
├── .editorconfig
├── .ember-cli
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── release-image.yml
│ ├── release-npm.yml
│ └── test.yml
├── .gitignore
├── .husky
├── commit-msg
└── pre-commit
├── .npmrc
├── .prettierignore
├── .template-lintrc-ci.js
├── .template-lintrc.js
├── .watchmanconfig
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── abilities
│ ├── absence-credit.js
│ ├── overtime-credit.js
│ ├── page.js
│ ├── report.js
│ └── user.js
├── adapters
│ ├── activity-block.js
│ └── application.js
├── analysis
│ ├── edit
│ │ ├── controller.js
│ │ ├── route.js
│ │ └── template.hbs
│ ├── index
│ │ ├── controller.js
│ │ ├── route.js
│ │ └── template.hbs
│ └── route.js
├── app.js
├── application
│ ├── route.js
│ └── template.hbs
├── breakpoints.js
├── components
│ ├── async-list
│ │ └── template.hbs
│ ├── attendance-slider
│ │ ├── component.js
│ │ └── template.hbs
│ ├── balance-donut
│ │ ├── component.js
│ │ └── template.hbs
│ ├── changed-warning
│ │ └── template.hbs
│ ├── customer-visible-icon
│ │ └── template.hbs
│ ├── date-buttons
│ │ ├── component.js
│ │ └── template.hbs
│ ├── date-navigation
│ │ ├── component.js
│ │ └── template.hbs
│ ├── duration-since
│ │ ├── component.js
│ │ └── template.hbs
│ ├── filter-sidebar
│ │ ├── component.js
│ │ ├── filter
│ │ │ └── template.hbs
│ │ ├── group
│ │ │ ├── component.js
│ │ │ ├── styles.scss
│ │ │ └── template.hbs
│ │ ├── label
│ │ │ └── template.hbs
│ │ └── template.hbs
│ ├── in-viewport
│ │ ├── component.js
│ │ └── template.hbs
│ ├── loading-icon
│ │ └── template.hbs
│ ├── magic-link-btn
│ │ ├── component.js
│ │ └── template.hbs
│ ├── magic-link-modal
│ │ ├── component.js
│ │ └── template.hbs
│ ├── no-mobile-message
│ │ └── template.hbs
│ ├── no-permission
│ │ └── template.hbs
│ ├── not-identical-warning
│ │ └── template.hbs
│ ├── optimized-power-select
│ │ ├── component.js
│ │ ├── custom-options
│ │ │ ├── customer-option.hbs
│ │ │ ├── project-option.hbs
│ │ │ ├── task-option.hbs
│ │ │ └── user-option.hbs
│ │ ├── custom-select
│ │ │ ├── task-selection.hbs
│ │ │ └── user-selection.hbs
│ │ ├── options
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ │ ├── template.hbs
│ │ └── trigger
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ ├── page-permission
│ │ └── template.hbs
│ ├── progress-tooltip
│ │ ├── component.js
│ │ └── template.hbs
│ ├── record-button
│ │ ├── component.js
│ │ └── template.hbs
│ ├── report-review-warning
│ │ ├── component.js
│ │ └── template.hbs
│ ├── report-row
│ │ ├── component.js
│ │ └── template.hbs
│ ├── scroll-container.hbs
│ ├── sort-header
│ │ ├── component.js
│ │ └── template.hbs
│ ├── statistic-list
│ │ ├── bar
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ │ ├── column
│ │ │ └── template.hbs
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-calendar
│ │ ├── component.js
│ │ ├── styles.scss
│ │ └── template.hbs
│ ├── sy-checkbox
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-checkmark
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-datepicker-btn
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-datepicker
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-durationpicker-day
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-durationpicker
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-modal-target
│ │ └── template.hbs
│ ├── sy-modal
│ │ ├── body
│ │ │ ├── styles.scss
│ │ │ └── template.hbs
│ │ ├── footer
│ │ │ └── template.hbs
│ │ ├── header
│ │ │ └── template.hbs
│ │ ├── overlay
│ │ │ ├── component.js
│ │ │ └── template.hbs
│ │ └── template.hbs
│ ├── sy-timepicker
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-toggle
│ │ ├── component.js
│ │ └── template.hbs
│ ├── sy-topnav
│ │ ├── component.js
│ │ └── template.hbs
│ ├── task-selection
│ │ ├── component.js
│ │ └── template.hbs
│ ├── timed-clock
│ │ ├── component.js
│ │ └── template.hbs
│ ├── tracking-bar
│ │ ├── component.js
│ │ └── template.hbs
│ ├── user-selection
│ │ ├── component.js
│ │ └── template.hbs
│ ├── vertical-collection
│ │ └── component.js
│ ├── weekly-overview-benchmark
│ │ ├── component.js
│ │ └── template.hbs
│ ├── weekly-overview-day
│ │ ├── component.js
│ │ └── template.hbs
│ ├── weekly-overview
│ │ ├── component.js
│ │ └── template.hbs
│ ├── welcome-modal
│ │ └── template.hbs
│ └── worktime-balance-chart
│ │ ├── component.js
│ │ └── template.hbs
├── controllers
│ └── qpcontroller.js
├── helpers
│ ├── balance-highlight-class.js
│ ├── format-duration.js
│ ├── humanize-duration.js
│ └── parse-django-duration.js
├── index.html
├── index
│ ├── activities
│ │ ├── controller.js
│ │ ├── edit
│ │ │ ├── controller.js
│ │ │ ├── route.js
│ │ │ └── template.hbs
│ │ ├── route.js
│ │ └── template.hbs
│ ├── attendances
│ │ ├── controller.js
│ │ ├── route.js
│ │ └── template.hbs
│ ├── controller.js
│ ├── reports
│ │ ├── controller.js
│ │ ├── route.js
│ │ └── template.hbs
│ ├── route.js
│ └── template.hbs
├── initializers
│ └── responsive.js
├── login
│ ├── route.js
│ └── template.hbs
├── models
│ ├── absence-balance.js
│ ├── absence-credit.js
│ ├── absence-type.js
│ ├── absence.js
│ ├── activity.js
│ ├── attendance.js
│ ├── billing-type.js
│ ├── cost-center.js
│ ├── customer-assignee.js
│ ├── customer-statistic.js
│ ├── customer.js
│ ├── employment.js
│ ├── location.js
│ ├── month-statistic.js
│ ├── overtime-credit.js
│ ├── project-assignee.js
│ ├── project-statistic.js
│ ├── project.js
│ ├── public-holiday.js
│ ├── report-intersection.js
│ ├── report.js
│ ├── task-assignee.js
│ ├── task-statistic.js
│ ├── task.js
│ ├── user-statistic.js
│ ├── user.js
│ ├── worktime-balance.js
│ └── year-statistic.js
├── no-access
│ ├── route.js
│ └── template.hbs
├── notfound
│ ├── route.js
│ └── template.hbs
├── projects
│ ├── controller.js
│ ├── route.js
│ └── template.hbs
├── protected
│ ├── controller.js
│ ├── route.js
│ └── template.hbs
├── router.js
├── serializers
│ ├── application.js
│ ├── attendance.js
│ └── employment.js
├── services
│ ├── autostart-tour.js
│ ├── fetch.js
│ ├── metadata-fetcher.js
│ ├── rejected-reports.js
│ ├── tour.js
│ ├── tracking.js
│ └── unverified-reports.js
├── sso-login
│ └── route.js
├── statistics
│ ├── controller.js
│ ├── route.js
│ └── template.hbs
├── styles
│ ├── activities.scss
│ ├── adcssy.scss
│ ├── analysis.scss
│ ├── app.scss
│ ├── attendances.scss
│ ├── badge.scss
│ ├── components
│ │ ├── attendance-slider.scss
│ │ ├── balance-donut.scss
│ │ ├── date-buttons.scss
│ │ ├── date-navigation.scss
│ │ ├── filter-sidebar--group.scss
│ │ ├── filter-sidebar--label.scss
│ │ ├── loading-icon.scss
│ │ ├── magic-link-btn.scss
│ │ ├── nav-top.scss
│ │ ├── progress-tooltip.scss
│ │ ├── record-button.scss
│ │ ├── scroll-container.scss
│ │ ├── sort-header.scss
│ │ ├── statistic-list-bar.scss
│ │ ├── sy-calendar.scss
│ │ ├── sy-checkbox.scss
│ │ ├── sy-datepicker.scss
│ │ ├── sy-durationpicker-day.scss
│ │ ├── sy-modal--footer.scss
│ │ ├── sy-modal--overlay.scss
│ │ ├── sy-toggle.scss
│ │ ├── timed-clock.scss
│ │ ├── tracking-bar.scss
│ │ ├── weekly-overview-benchmark.scss
│ │ ├── weekly-overview-day.scss
│ │ ├── weekly-overview.scss
│ │ └── welcome-modal.scss
│ ├── ember-power-select-custom.scss
│ ├── filter-sidebar.scss
│ ├── form-list.scss
│ ├── loader.scss
│ ├── login.scss
│ ├── projects.scss
│ ├── reports.scss
│ ├── statistics.scss
│ ├── toolbar.scss
│ ├── tour.scss
│ ├── users-navigation.scss
│ ├── users.scss
│ └── variables.scss
├── tours
│ ├── index.js
│ └── index
│ │ ├── activities.js
│ │ ├── attendances.js
│ │ └── reports.js
├── transforms
│ ├── django-date.js
│ ├── django-datetime.js
│ ├── django-duration.js
│ ├── django-time.js
│ ├── django-workdays.js
│ └── moment.js
├── users
│ ├── edit
│ │ ├── controller.js
│ │ ├── credits
│ │ │ ├── absence-credits
│ │ │ │ ├── edit
│ │ │ │ │ ├── controller.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── template.hbs
│ │ │ │ └── new
│ │ │ │ │ └── route.js
│ │ │ ├── index
│ │ │ │ ├── controller.js
│ │ │ │ ├── route.js
│ │ │ │ └── template.hbs
│ │ │ ├── overtime-credits
│ │ │ │ ├── edit
│ │ │ │ │ ├── controller.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── template.hbs
│ │ │ │ └── new
│ │ │ │ │ └── route.js
│ │ │ ├── route.js
│ │ │ └── template.hbs
│ │ ├── index
│ │ │ ├── controller.js
│ │ │ ├── route.js
│ │ │ └── template.hbs
│ │ ├── responsibilities
│ │ │ ├── controller.js
│ │ │ ├── route.js
│ │ │ └── template.hbs
│ │ ├── route.js
│ │ └── template.hbs
│ ├── index
│ │ ├── controller.js
│ │ ├── route.js
│ │ └── template.hbs
│ ├── route.js
│ └── template.hbs
├── utils
│ ├── format-duration.js
│ ├── humanize-duration.js
│ ├── parse-django-duration.js
│ ├── query-params.js
│ ├── serialize-moment.js
│ └── url.js
├── validations
│ ├── absence-credit.js
│ ├── absence.js
│ ├── activity.js
│ ├── attendance.js
│ ├── intersection.js
│ ├── multiple-absence.js
│ ├── overtime-credit.js
│ ├── project.js
│ ├── report.js
│ └── task.js
└── validators
│ ├── intersection-task.js
│ ├── moment.js
│ └── null-or-not-blank.js
├── config
├── coverage.js
├── dependency-lint.js
├── deprecation-workflow.js
├── ember-cli-update.json
├── environment.js
├── icons.js
├── optional-features.json
└── targets.js
├── contrib
└── nginx.conf
├── docker-compose.yml
├── docker-entrypoint.sh
├── ember-cli-build.js
├── mirage
├── config.js
├── factories
│ ├── absence-balance.js
│ ├── absence-credit.js
│ ├── absence-type.js
│ ├── absence.js
│ ├── activity.js
│ ├── attendance.js
│ ├── billing-type.js
│ ├── cost-center.js
│ ├── customer-statistic.js
│ ├── customer.js
│ ├── employment.js
│ ├── location.js
│ ├── month-statistic.js
│ ├── overtime-credit.js
│ ├── project-assignee.js
│ ├── project-statistic.js
│ ├── project.js
│ ├── public-holiday.js
│ ├── report-intersection.js
│ ├── report.js
│ ├── task-statistic.js
│ ├── task.js
│ ├── user-statistic.js
│ ├── user.js
│ ├── worktime-balance.js
│ └── year-statistic.js
├── fixtures
│ └── absence-types.js
├── helpers
│ └── duration.js
├── scenarios
│ └── default.js
└── serializers
│ └── application.js
├── package.json
├── pnpm-lock.yaml
├── public
├── assets
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── logo.png
│ ├── logo.svg
│ └── logo_text.png
├── crossdomain.xml
└── robots.txt
├── renovate.json
├── testem.js
├── tests
├── .eslintrc.js
├── acceptance
│ ├── analysis-edit-test.js
│ ├── analysis-test.js
│ ├── auth-test.js
│ ├── external-employee-test.js
│ ├── index-activities-edit-test.js
│ ├── index-activities-test.js
│ ├── index-attendances-test.js
│ ├── index-reports-test.js
│ ├── index-test.js
│ ├── magic-link-test.js
│ ├── notfound-test.js
│ ├── project-test.js
│ ├── statistics-test.js
│ ├── tour-test.js
│ ├── users-edit-credits-absence-credit-test.js
│ ├── users-edit-credits-overtime-credit-test.js
│ ├── users-edit-credits-test.js
│ ├── users-edit-responsibilities-test.js
│ ├── users-edit-test.js
│ └── users-test.js
├── helpers
│ ├── index.js
│ ├── responsive.js
│ ├── session-mock.js
│ ├── task-select.js
│ ├── tracking-mock.js
│ └── user-select.js
├── index.html
├── integration
│ └── components
│ │ ├── async-list
│ │ └── component-test.js
│ │ ├── attendance-slider
│ │ └── component-test.js
│ │ ├── balance-donut
│ │ └── component-test.js
│ │ ├── changed-warning
│ │ └── component-test.js
│ │ ├── customer-visible-icon
│ │ └── component-test.js
│ │ ├── date-buttons
│ │ └── component-test.js
│ │ ├── date-navigation
│ │ └── component-test.js
│ │ ├── duration-since
│ │ └── component-test.js
│ │ ├── filter-sidebar
│ │ ├── component-test.js
│ │ ├── filter
│ │ │ └── component-test.js
│ │ ├── group
│ │ │ └── component-test.js
│ │ └── label
│ │ │ └── component-test.js
│ │ ├── in-viewport
│ │ └── component-test.js
│ │ ├── loading-icon
│ │ └── component-test.js
│ │ ├── no-mobile-message
│ │ └── component-test.js
│ │ ├── no-permission
│ │ └── component-test.js
│ │ ├── not-identical-warning
│ │ └── component-test.js
│ │ ├── optimized-power-select
│ │ └── component-test.js
│ │ ├── progress-tooltip
│ │ └── component-test.js
│ │ ├── record-button
│ │ └── component-test.js
│ │ ├── report-review-warning
│ │ └── component-test.js
│ │ ├── report-row
│ │ └── component-test.js
│ │ ├── sort-header
│ │ └── component-test.js
│ │ ├── statistic-list
│ │ ├── bar
│ │ │ └── component-test.js
│ │ ├── column
│ │ │ └── component-test.js
│ │ └── component-test.js
│ │ ├── sy-calendar
│ │ └── component-test.js
│ │ ├── sy-checkbox
│ │ └── component-test.js
│ │ ├── sy-checkmark
│ │ └── component-test.js
│ │ ├── sy-datepicker-btn
│ │ └── component-test.js
│ │ ├── sy-datepicker
│ │ └── component-test.js
│ │ ├── sy-durationpicker-day
│ │ └── component-test.js
│ │ ├── sy-durationpicker
│ │ └── component-test.js
│ │ ├── sy-modal-target
│ │ └── component-test.js
│ │ ├── sy-modal
│ │ ├── body
│ │ │ └── component-test.js
│ │ ├── component-test.js
│ │ ├── footer
│ │ │ └── component-test.js
│ │ ├── header
│ │ │ └── component-test.js
│ │ └── overlay
│ │ │ └── component-test.js
│ │ ├── sy-timepicker
│ │ └── component-test.js
│ │ ├── sy-toggle
│ │ └── component-test.js
│ │ ├── sy-topnav
│ │ └── component-test.js
│ │ ├── task-selection
│ │ └── component-test.js
│ │ ├── timed-clock
│ │ └── component-test.js
│ │ ├── tracking-bar
│ │ └── component-test.js
│ │ ├── user-selection
│ │ └── component-test.js
│ │ ├── weekly-overview-benchmark
│ │ └── component-test.js
│ │ ├── weekly-overview-day
│ │ └── component-test.js
│ │ ├── weekly-overview
│ │ └── component-test.js
│ │ ├── welcome-modal
│ │ └── component-test.js
│ │ └── worktime-balance-chart
│ │ └── component-test.js
├── test-helper.js
└── unit
│ ├── abilities
│ └── report-test.js
│ ├── analysis
│ ├── edit
│ │ ├── controller-test.js
│ │ └── route-test.js
│ ├── index
│ │ ├── controller-test.js
│ │ └── route-test.js
│ └── route-test.js
│ ├── controllers
│ └── qpcontroller
│ │ └── controller-test.js
│ ├── helpers
│ ├── balance-highlight-class-test.js
│ ├── format-duration-test.js
│ ├── humanize-duration-test.js
│ └── parse-django-duration-test.js
│ ├── index
│ ├── activities
│ │ ├── controller-test.js
│ │ ├── edit
│ │ │ ├── controller-test.js
│ │ │ └── route-test.js
│ │ └── route-test.js
│ ├── attendances
│ │ ├── controller-test.js
│ │ └── route-test.js
│ ├── controller-test.js
│ ├── reports
│ │ ├── controller-test.js
│ │ └── route-test.js
│ └── route-test.js
│ ├── login
│ └── route-test.js
│ ├── models
│ ├── absence-balance-test.js
│ ├── activity-test.js
│ ├── attendance-test.js
│ ├── billing-type-test.js
│ ├── cost-center-test.js
│ ├── customer-statistic-test.js
│ ├── customer-test.js
│ ├── employment-test.js
│ ├── location-test.js
│ ├── month-statistic-test.js
│ ├── overtime-credit-test.js
│ ├── project-statistic-test.js
│ ├── project-test.js
│ ├── public-holiday-test.js
│ ├── report-intersection-test.js
│ ├── report-test.js
│ ├── task-statistic-test.js
│ ├── task-test.js
│ ├── user-statistic-test.js
│ ├── user-test.js
│ ├── worktime-balance-test.js
│ └── year-statistic-test.js
│ ├── no-access
│ └── route-test.js
│ ├── notfound
│ └── route-test.js
│ ├── projects
│ ├── controller-test.js
│ └── route-test.js
│ ├── protected
│ ├── controller-test.js
│ └── route-test.js
│ ├── serializers
│ ├── attendance-test.js
│ └── employment-test.js
│ ├── services
│ ├── autostart-tour-test.js
│ ├── fetch-test.js
│ ├── metadata-fetcher-test.js
│ ├── rejected-reports-test.js
│ ├── tracking-test.js
│ └── unverified-reports-test.js
│ ├── sso-login
│ └── route-test.js
│ ├── statistics
│ ├── controller-test.js
│ └── route-test.js
│ ├── transforms
│ ├── django-date-test.js
│ ├── django-datetime-test.js
│ ├── django-duration-test.js
│ ├── django-time-test.js
│ └── django-workdays-test.js
│ ├── users
│ ├── edit
│ │ ├── controller-test.js
│ │ ├── credits
│ │ │ ├── absence-credits
│ │ │ │ ├── edit
│ │ │ │ │ ├── controller-test.js
│ │ │ │ │ └── route-test.js
│ │ │ │ └── new
│ │ │ │ │ └── route-test.js
│ │ │ ├── index
│ │ │ │ ├── controller-test.js
│ │ │ │ └── route-test.js
│ │ │ ├── overtime-credits
│ │ │ │ ├── edit
│ │ │ │ │ ├── controller-test.js
│ │ │ │ │ └── route-test.js
│ │ │ │ └── new
│ │ │ │ │ └── route-test.js
│ │ │ └── route-test.js
│ │ ├── index
│ │ │ ├── controller-test.js
│ │ │ └── route-test.js
│ │ ├── responsibilities
│ │ │ ├── controller-test.js
│ │ │ └── route-test.js
│ │ └── route-test.js
│ ├── index
│ │ ├── controller-test.js
│ │ └── route-test.js
│ └── route-test.js
│ ├── utils
│ ├── format-duration-test.js
│ ├── humanize-duration-test.js
│ ├── parse-django-duration-test.js
│ ├── query-params-test.js
│ └── url-test.js
│ └── validators
│ ├── moment-test.js
│ └── null-or-not-blank-test.js
└── vendor
└── .gitkeep
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bower_components/
3 | .git/
4 | tmp/
5 | dist/
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | end_of_line = lf
9 | charset = utf-8
10 | trim_trailing_whitespace = true
11 | insert_final_newline = true
12 | indent_style = space
13 | indent_size = 2
14 |
15 | [*.hbs]
16 | insert_final_newline = false
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": true,
9 |
10 | /**
11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
13 | */
14 | "isTypeScriptProject": false
15 | }
16 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .*/
17 | .eslintcache
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /npm-shrinkwrap.json.ember-try
23 | /package.json.ember-try
24 | /package-lock.json.ember-try
25 |
26 | # template lint ci config
27 | .template-lintrc-ci.js
28 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | "use-strict";
2 |
3 | module.exports = {
4 | extends: ["@adfinis/eslint-config/ember-app"],
5 | rules: {
6 | "ember/no-actions-hash": "warn",
7 | "ember/no-component-lifecycle-hooks": "warn",
8 | "ember/no-mixins": "warn",
9 | "ember/no-new-mixins": "warn",
10 | "ember/no-classic-classes": "warn",
11 | "ember/no-classic-components": "warn",
12 | "ember/no-get": "warn",
13 | "ember/no-observers": "warn",
14 | "qunit/no-assert-equal": "warn",
15 | "ember/require-tagless-components": "warn",
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ""
5 | labels: ""
6 | assignees: ""
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: github-actions
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | day: "friday"
8 | time: "12:00"
9 | timezone: "Europe/Zurich"
10 | - package-ecosystem: npm
11 | directory: "/"
12 | schedule:
13 | interval: "weekly"
14 | day: "friday"
15 | time: "12:00"
16 | timezone: "Europe/Zurich"
17 | open-pull-requests-limit: 10
18 | versioning-strategy: increase
19 |
--------------------------------------------------------------------------------
/.github/workflows/release-npm.yml:
--------------------------------------------------------------------------------
1 | name: Release npm package
2 |
3 | on: workflow_dispatch
4 |
5 | jobs:
6 | release:
7 | name: Release
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v4
11 | with:
12 | fetch-depth: 0
13 | persist-credentials: false
14 |
15 | - name: Install pnpm
16 | uses: pnpm/action-setup@v2.4.0
17 | with:
18 | version: 7
19 |
20 | - uses: actions/setup-node@v4
21 | with:
22 | node-version: "18"
23 | cache: "pnpm"
24 |
25 | - name: Install dependencies
26 | run: pnpm install
27 |
28 | - name: Release on NPM
29 | run: pnpm semantic-release
30 | env:
31 | GH_TOKEN: ${{ secrets.GH_TOKEN }}
32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | /.sass-cache
13 | /.eslintcache
14 | /connect.lock
15 | /coverage/*
16 | /libpeerconnection.log
17 | npm-debug.log*
18 | testem.log
19 | *.swp
20 | *.orig
21 |
22 | # vscode
23 | jsconfig.json
24 |
25 | /.vscode/
26 | /.idea/
27 |
28 | # ember-try
29 | /.node_modules.ember-try/
30 | /bower.json.ember-try
31 | /npm-shrinkwrap.json.ember-try
32 | /package.json.ember-try
33 | /package-lock.json.ember-try
34 | /yarn.lock.ember-try
35 |
36 | # broccoli-debug
37 | /DEBUG/
38 |
--------------------------------------------------------------------------------
/.husky/commit-msg:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # skip in CI
5 | [ -n "$CI" ] && exit 0
6 |
7 | # lint commit message
8 | pnpm commitlint --edit $1
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | # skip in CI
5 | [ -n "$CI" ] && exit 0
6 |
7 | # lint staged files
8 | pnpm lint-staged
9 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | # TODO: delete this when updating to pnpm v8
2 | auto-install-peers=false
3 |
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # unconventional js
2 | /blueprints/*/files/
3 | /vendor/
4 |
5 | # compiled output
6 | /dist/
7 | /tmp/
8 |
9 | # dependencies
10 | /bower_components/
11 | /node_modules/
12 |
13 | # misc
14 | /coverage/
15 | !.*
16 | .eslintcache
17 | .lint-todo/
18 |
19 | # ember-try
20 | /.node_modules.ember-try/
21 | /bower.json.ember-try
22 | /npm-shrinkwrap.json.ember-try
23 | /package.json.ember-try
24 | /package-lock.json.ember-try
25 | /yarn.lock.ember-try
26 |
--------------------------------------------------------------------------------
/.template-lintrc-ci.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | extends: "recommended",
5 | rules: {
6 | // following rules are for temporary use only, delete when ember 4.0 ready
7 | "no-action": "warn",
8 | "no-curly-component-invocation": "warn",
9 | "no-duplicate-id": "warn",
10 | "no-link-to-positional-params": "warn",
11 | "no-link-to-tagname": "warn",
12 | "no-invalid-interactive": "warn",
13 | "no-implicit-this": "warn",
14 | "no-passed-in-event-handlers": "warn",
15 | "no-positional-data-test-selectors": "warn",
16 | "no-unknown-arguments-for-builtin-components": "warn",
17 | "no-with": "warn",
18 | "no-yield-only": "warn",
19 | "require-input-label": "warn",
20 | "require-has-block-helper": "warn",
21 | "require-presentational-children": "warn",
22 | },
23 | };
24 |
--------------------------------------------------------------------------------
/.template-lintrc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | extends: "recommended",
5 | };
6 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM danlynn/ember-cli:3.28.5 as build
2 |
3 | # pin pnpm to v7 as long as we are on the ember-cli:3.28.5 image
4 | RUN npm install -g pnpm@7
5 |
6 | COPY package.json pnpm-lock.yaml /myapp/
7 |
8 | RUN pnpm fetch
9 |
10 | COPY . /myapp/
11 |
12 | RUN pnpm install --frozen-lockfile --offline
13 |
14 | RUN pnpm run build --environment=production
15 |
16 | FROM nginx:alpine
17 |
18 | COPY --from=build /myapp/dist /var/www/html
19 | COPY ./contrib/nginx.conf /etc/nginx/conf.d/default.conf
20 |
21 | WORKDIR /var/www/html
22 |
23 | COPY ./docker-entrypoint.sh /
24 | ENV TIMED_SSO_CLIENT_HOST https://sso.example.com/auth/realms/example/protocol/openid-connect
25 | ENV TIMED_SSO_CLIENT_ID timed
26 |
27 | EXPOSE 80
28 |
29 | ENTRYPOINT ["/docker-entrypoint.sh"]
30 | CMD ["nginx", "-g", "daemon off;"]
31 |
--------------------------------------------------------------------------------
/app/abilities/absence-credit.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import { Ability } from "ember-can";
3 |
4 | export default class AbsenceCreditAbility extends Ability {
5 | @service session;
6 |
7 | get user() {
8 | return this.session.data.user;
9 | }
10 | get canEdit() {
11 | return this.user.isSuperuser;
12 | }
13 | get canCreate() {
14 | return this.user.isSuperuser;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/abilities/overtime-credit.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import { Ability } from "ember-can";
3 |
4 | export default class OvertimeCreditAbility extends Ability {
5 | @service session;
6 |
7 | get user() {
8 | return this.session.data.user;
9 | }
10 | get canEdit() {
11 | return this.user.isSuperuser;
12 | }
13 | get canCreate() {
14 | return this.user.isSuperuser;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/abilities/page.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import { Ability } from "ember-can";
3 |
4 | export default class PageAbility extends Ability {
5 | @service session;
6 |
7 | get user() {
8 | return this.session.data.user;
9 | }
10 | get canAccess() {
11 | if (!this.user) {
12 | return false;
13 | }
14 |
15 | return !this.user.activeEmployment?.isExternal || this.user.isReviewer;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/abilities/report.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import { Ability } from "ember-can";
3 |
4 | export default class ReportAbility extends Ability {
5 | @service session;
6 |
7 | get user() {
8 | return this.session.data.user;
9 | }
10 |
11 | get canEdit() {
12 | const isEditable =
13 | this.user?.isSuperuser ||
14 | (!this.model?.verifiedBy?.get("id") &&
15 | // eslint-disable-next-line ember/no-get
16 | (this.model?.user?.get("id") === this.user?.get("id") ||
17 | // eslint-disable-next-line ember/no-get
18 | (this.model?.user?.get("supervisors") ?? [])
19 | .mapBy("id")
20 | .includes(this.user?.get("id"))));
21 | const isReviewer =
22 | (this.model?.taskAssignees ?? [])
23 | .concat(
24 | this.model?.projectAssignees ?? [],
25 | this.model?.customerAssignees ?? []
26 | )
27 | .mapBy("user.id")
28 | .includes(this.user?.get("id")) && !this.model?.verifiedBy?.get("id");
29 | return isEditable || isReviewer;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/abilities/user.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import { Ability } from "ember-can";
3 |
4 | export default class UserAbility extends Ability {
5 | @service session;
6 |
7 | get user() {
8 | return this.session.data.user;
9 | }
10 |
11 | get canRead() {
12 | return (
13 | this.user?.isSuperuser ||
14 | this.user?.id === this.model.id ||
15 | this.model.supervisors.mapBy("id").includes(this.user?.id)
16 | );
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/adapters/activity-block.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-adapters
4 | * @public
5 | */
6 | import ApplicationAdapter from "timed/adapters/application";
7 |
8 | /**
9 | * The activity block adapter
10 | *
11 | * @class ActivityBlockAdapter
12 | * @extends ApplicationAdapter
13 | * @public
14 | */
15 | export default ApplicationAdapter.extend({
16 | /**
17 | * Custom url for updating records
18 | *
19 | * This causes a reload of the activity so we don't have to do the reload
20 | * ourselves
21 | *
22 | * @method urlForUpdateRecord
23 | * @return {String} The URL
24 | * @public
25 | */
26 | urlForUpdateRecord(...args) {
27 | return `${this._super(...args)}?include=activity`;
28 | },
29 |
30 | /**
31 | * Custom url for creating records
32 | *
33 | * This causes a reload of the activity so we don't have to do the reload
34 | * ourselves
35 | *
36 | * @method urlForCreateRecord
37 | * @return {String} The URL
38 | * @public
39 | */
40 | urlForCreateRecord(...args) {
41 | return `${this._super(...args)}?include=activity`;
42 | },
43 | });
44 |
--------------------------------------------------------------------------------
/app/adapters/application.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-adapters
4 | * @public
5 | */
6 | import OIDCJSONAPIAdapter from "ember-simple-auth-oidc/adapters/oidc-json-api-adapter";
7 |
8 | /**
9 | * The application adapter
10 | *
11 | * @class ApplicationAdapter
12 | * @extends OIDCAdapterMixin
13 | * @uses EmberSimpleAuthOIDC.OIDCAdapterMixin
14 | * @public
15 | */
16 | export default class ApplicationAdapter extends OIDCJSONAPIAdapter {
17 | namespace = "api/v1";
18 | }
19 |
--------------------------------------------------------------------------------
/app/analysis/edit/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 | import { resetQueryParams } from "timed/utils/query-params";
3 |
4 | export default class AnalysisEditRoute extends Route {
5 | setupController(controller) {
6 | if (controller.id) {
7 | controller.id = controller.id.split(",");
8 | }
9 | controller.intersection.perform();
10 | }
11 | resetController(controller, isExiting, transition) {
12 | if (isExiting && transition.targetName !== "error") {
13 | resetQueryParams(controller, controller.queryParams);
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/analysis/index/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 | import { next } from "@ember/runloop";
3 |
4 | export default class AnalysisIndexRoute extends Route {
5 | queryParams = {
6 | rejected: {
7 | refreshModel: true,
8 | },
9 | verified: {
10 | refreshModel: true,
11 | },
12 | };
13 |
14 | model() {
15 | /* eslint-disable-next-line ember/no-controller-access-in-routes */
16 | const controller = this.controllerFor("analysis.index");
17 | const skipReset = controller.skipResetOnSetup;
18 | next(() => {
19 | if (!skipReset) {
20 | controller._reset();
21 | }
22 | });
23 | }
24 |
25 | setupController(controller) {
26 | controller.prefetchData.perform();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/analysis/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class AnalysisRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/application/route.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-routes
4 | * @public
5 | */
6 | import Route from "@ember/routing/route";
7 | import { inject as service } from "@ember/service";
8 |
9 | /**
10 | * The application route
11 | *
12 | * @class ApplicationRoute
13 | * @extends Ember.Route
14 | * @uses EmberSimpleAuth.ApplicationRouteMixin
15 | * @public
16 | */
17 | export default class ApplicationRoute extends Route {
18 | @service session;
19 |
20 | async beforeModel() {
21 | await this.session.setup();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/application/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{outlet}}
7 |
--------------------------------------------------------------------------------
/app/breakpoints.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mo: "(max-width: 479px)",
3 | xs: "(min-width: 480px) and (max-width: 767px)",
4 | sm: "(min-width: 768px) and (max-width: 991px)",
5 | md: "(min-width: 992px) and (max-width: 1199px)",
6 | lg: "(min-width: 1200px) and (max-width: 1439px)",
7 | xl: "(min-width: 1440px)",
8 | };
9 |
--------------------------------------------------------------------------------
/app/components/async-list/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if @data.isRunning}}
2 |
3 |
4 |
5 | {{else if @data.isError}}
6 |
7 |
8 |
9 |
Oops... Something went wrong
10 |
11 | Have you tried turning it off and on again?
12 |
13 | Please try refreshing the page.
14 |
15 |
16 |
17 | {{else if (not @data.value.length)}}
18 |
19 | {{yield "empty" @data.value}}
20 |
21 | {{else}}
22 | {{yield "body" @data.value}}
23 | {{/if}}
24 |
--------------------------------------------------------------------------------
/app/components/attendance-slider/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | {{#each this.labels as |label|}}
17 |
21 |
22 | {{label.value}}
23 |
24 |
25 | {{/each}}
26 |
27 |
28 |
29 | {{this.duration}}
30 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/app/components/changed-warning/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/components/customer-visible-icon/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/components/date-buttons/template.hbs:
--------------------------------------------------------------------------------
1 | {{#each this.choices as |choice index|}}
2 | {{choice}}
8 | {{/each}}
9 |
--------------------------------------------------------------------------------
/app/components/date-navigation/template.hbs:
--------------------------------------------------------------------------------
1 |
37 |
--------------------------------------------------------------------------------
/app/components/duration-since/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{format-duration this.duration}}
3 |
--------------------------------------------------------------------------------
/app/components/filter-sidebar/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 | import { tracked } from "@glimmer/tracking";
3 |
4 | export default class FilterSidebar extends Component {
5 | @tracked visible = false;
6 | @tracked destination;
7 |
8 | constructor(...args) {
9 | super(...args);
10 | this.destination = document.getElementById("filter-sidebar-target");
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/components/filter-sidebar/group/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 | import { tracked } from "@glimmer/tracking";
3 |
4 | export default class Group extends Component {
5 | @tracked expanded = false;
6 | }
7 |
--------------------------------------------------------------------------------
/app/components/filter-sidebar/group/styles.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/app/components/filter-sidebar/group/styles.scss
--------------------------------------------------------------------------------
/app/components/filter-sidebar/group/template.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/components/filter-sidebar/label/template.hbs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/components/in-viewport/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import Component from "@glimmer/component";
3 | import { tracked } from "@glimmer/tracking";
4 |
5 | export default class InViewport extends Component {
6 | @tracked rootSelector = "body";
7 | @tracked rootMargin = 0;
8 | _observer = null;
9 |
10 | @action
11 | registerObserver(element) {
12 | const observer = new IntersectionObserver(
13 | ([{ isIntersecting }]) => {
14 | if (isIntersecting) {
15 | return (this.args["on-enter-viewport"] ?? (() => {}))();
16 | }
17 |
18 | return (this.args["on-exit-viewport"] ?? (() => {}))();
19 | },
20 | {
21 | root: document.querySelector(this.rootSelector),
22 | rootMargin: `${this.rootMargin}px`,
23 | }
24 | );
25 |
26 | this._observer = observer;
27 |
28 | // eslint-disable-next-line ember/no-observers
29 | observer.observe(element);
30 | }
31 |
32 | willDestroy(...args) {
33 | super.willDestroy(...args);
34 | this._observer?.disconnect();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/components/in-viewport/template.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
--------------------------------------------------------------------------------
/app/components/loading-icon/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/components/magic-link-btn/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 | import { tracked } from "@glimmer/tracking";
3 |
4 | export default class MagicLinkBtn extends Component {
5 | @tracked isModalVisible = false;
6 | }
7 |
--------------------------------------------------------------------------------
/app/components/magic-link-btn/template.hbs:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 | {{#if this.isModalVisible}}
12 |
13 | {{/if}}
14 |
--------------------------------------------------------------------------------
/app/components/no-mobile-message/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Sorry, this page doesn't work on mobile!
4 |
5 | The data on this page is taking up too much space for your currently used
6 | device.
7 |
8 |
9 | Try opening this page on a larger device, or go back to the
10 | home page !
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/components/no-permission/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Halt!
4 |
5 | You are not supposed to be here...
6 | Please leave and do not talk about it,
7 | ever !
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/components/not-identical-warning/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import Component from "@glimmer/component";
3 |
4 | export default class OptimizedPowerSelectComponent extends Component {
5 | get extra() {
6 | return this.args.extra ?? {};
7 | }
8 |
9 | @action
10 | onFocus({ actions, isOpen }) {
11 | if (!isOpen) {
12 | actions.open();
13 | }
14 | }
15 |
16 | @action
17 | onKeydown(select, e) {
18 | // this implementation is heavily inspired by the enter key handling of EPS
19 | // https://github.com/cibernox/ember-power-select/blob/6e3d5781a105515b915d407d571698c57290f674/addon/components/power-select.ts#L519
20 | if (e.keyCode === 9 && select.isOpen && select.highlighted !== undefined) {
21 | select.actions.choose(select.highlighted, e);
22 | e.stopImmediatePropagation();
23 | return false;
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-options/customer-option.hbs:
--------------------------------------------------------------------------------
1 |
8 | {{#if @option.isTask}}
9 |
10 |
11 |
12 | {{@option.project.customer.name}}
13 | >
14 | {{@option.project.name}}
15 | {{@option.name}}
16 |
17 |
18 | {{else}}
19 | {{@option.name}}
20 | {{/if}}
21 | {{#if @option.archived}}
22 |
23 | {{/if}}
24 |
25 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-options/project-option.hbs:
--------------------------------------------------------------------------------
1 |
6 | {{#if (and @current (or (media "isMd") (media "isLg") (media "isXl")))}}
7 |
12 | {{/if}}
13 | {{@option.name}}
14 | {{#if @option.archived}}
15 |
16 | {{/if}}
17 |
18 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-options/task-option.hbs:
--------------------------------------------------------------------------------
1 |
6 | {{#if (and @current (or (media "isMd") (media "isLg") (media "isXl")))}}
7 |
12 | {{/if}}
13 | {{@option.name}}
14 | {{#if @option.archived}}
15 |
16 | {{/if}}
17 |
18 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-options/user-option.hbs:
--------------------------------------------------------------------------------
1 |
5 | {{@option.longName}}
6 | {{#unless @option.isActive}}
7 |
8 | {{/unless}}
9 |
10 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-select/task-selection.hbs:
--------------------------------------------------------------------------------
1 | {{@selected.name}}
2 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/custom-select/user-selection.hbs:
--------------------------------------------------------------------------------
1 | {{@selected.longName}}
2 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/template.hbs:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/app/components/optimized-power-select/trigger/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import Component from "@glimmer/component";
3 |
4 | export default class OptimizedPowerSelectTriggerComponent extends Component {
5 | @action
6 | clear(e) {
7 | e.stopPropagation();
8 | this.args.select.actions.select(null);
9 | if (e.type === "touchstart") {
10 | return false;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/components/page-permission/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if (cannot "access page")}}
2 |
3 |
Access forbidden
4 | You do not have the permission to access this page
5 |
6 | {{else}}
7 | {{yield}}
8 | {{/if}}
--------------------------------------------------------------------------------
/app/components/record-button/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 |
3 | export default class RecordButton extends Component {
4 | get active() {
5 | return this.args.recording && this.args.activity?.id;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/components/record-button/template.hbs:
--------------------------------------------------------------------------------
1 |
28 |
--------------------------------------------------------------------------------
/app/components/report-review-warning/component.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import Component from "@glimmer/component";
3 |
4 | export default class ReportReviewWarning extends Component {
5 | @service session;
6 |
7 | @service unverifiedReports;
8 |
9 | @service rejectedReports;
10 | }
11 |
--------------------------------------------------------------------------------
/app/components/scroll-container.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
7 |
--------------------------------------------------------------------------------
/app/components/sort-header/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import Component from "@glimmer/component";
3 |
4 | export default class SortHeader extends Component {
5 | get direction() {
6 | return this.args.current?.startsWith("-") ? "down" : "up";
7 | }
8 |
9 | get getColname() {
10 | return this.args.current?.startsWith("-")
11 | ? this.args.current.substring(1)
12 | : this.args.current;
13 | }
14 |
15 | get active() {
16 | return this.getColname === this.args.by;
17 | }
18 |
19 | @action
20 | click() {
21 | const by = this.args.by;
22 | const sort = this.active && this.direction === "down" ? by : `-${by}`;
23 |
24 | this.args.update(sort);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/components/sort-header/template.hbs:
--------------------------------------------------------------------------------
1 | {{! template-lint-disable}}
2 |
10 |
--------------------------------------------------------------------------------
/app/components/statistic-list/bar/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 |
3 | export default class StatisticListBar extends Component {
4 | get didFinishEffortsInBudget() {
5 | return (
6 | this.args.remaining === 0 &&
7 | !this.didFinishEffortsOverBudget &&
8 | this.args.archived
9 | );
10 | }
11 |
12 | get didFinishEffortsOverBudget() {
13 | return this.args.value > this.args.goal;
14 | }
15 |
16 | get spentEffortsBarColor() {
17 | if (this.didFinishEffortsInBudget) {
18 | return "strong-success";
19 | }
20 | if (this.didFinishEffortsOverBudget) {
21 | return "strong-danger";
22 | }
23 | return "";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/statistic-list/bar/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | {{#if (and @remaining (lte @remaining 1))}}
9 |
15 | {{/if}}
16 | {{#if @goal}}
17 |
22 | {{/if}}
23 |
24 |
--------------------------------------------------------------------------------
/app/components/statistic-list/column/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if (eq @layout "DURATION")}}
3 | {{humanize-duration @value false}}
4 | {{else if (eq @layout "MONTH")}}
5 | {{moment-format (moment @value "M") "MMMM"}}
6 | {{else}}
7 | {{@value}}
8 | {{/if}}
9 |
10 |
--------------------------------------------------------------------------------
/app/components/sy-calendar/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import PowerCalendarComponent from "ember-power-calendar/components/power-calendar";
3 | import moment from "moment";
4 |
5 | const CURRENT_YEAR = moment().year();
6 |
7 | const YEARS_IN_FUTURE = 5;
8 |
9 | export default class SyCalendar extends PowerCalendarComponent {
10 | months = moment.months();
11 |
12 | years = [...new Array(40).keys()].map(
13 | (i) => `${CURRENT_YEAR + YEARS_IN_FUTURE - i}`
14 | );
15 |
16 | @action
17 | changeCenter(unit, calendar, e) {
18 | const newCenter = moment(calendar.center)[unit](e.target.value);
19 | calendar.actions.changeCenter(newCenter);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/components/sy-calendar/styles.scss:
--------------------------------------------------------------------------------
1 | & {
2 | @include ember-power-calendar($cell-size: 35px);
3 |
4 | .ember-power-calendar-nav-control {
5 | color: $color-primary;
6 | cursor: pointer;
7 |
8 | }
9 |
10 | .nav-select-month,
11 | .nav-select-year {
12 | position: relative;
13 |
14 | select {
15 | position: absolute;
16 | top: 0; left: 0; right: 0; bottom: 0;
17 | opacity: 0;
18 | }
19 | }
20 |
21 | .ember-power-calendar-day {
22 | cursor: pointer;
23 | transition: background-color 300ms ease, color 300ms ease;
24 |
25 | &--focused {
26 | box-shadow: inset 0 -2px 0 0 $color-primary;
27 | }
28 |
29 | &--selected {
30 | color: rgb(255,255,255);
31 | background-color: lighten($color-primary, 20%);
32 |
33 | &:hover {
34 | color: rgb(255,255,255);
35 | background-color: lighten($color-primary, 30%);
36 | }
37 | }
38 | }
39 | }
40 |
41 | &.sy-datepicker {
42 | border: 1px solid $color-border;
43 | padding: 0.5rem;
44 | box-shadow: 2px 2px 10px rgba(0,0,0,0.2);
45 | }
46 |
--------------------------------------------------------------------------------
/app/components/sy-checkbox/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import { guidFor } from "@ember/object/internals";
3 | import Component from "@glimmer/component";
4 |
5 | /**
6 | * Component for an adcssy styled checkbox
7 | *
8 | * @class SyCheckboxComponent
9 | * @extends Ember.Component
10 | * @public
11 | */
12 | export default class SyCheckbox extends Component {
13 | constructor(...args) {
14 | super(...args);
15 |
16 | this._checkboxElementId = guidFor(this);
17 | }
18 |
19 | get checkboxElementId() {
20 | return this._checkboxElementId;
21 | }
22 |
23 | @action
24 | handleCheckBox(element) {
25 | if (this.args.checked === null) {
26 | element.indeterminate = true;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/components/sy-checkbox/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 | {{#if (has-block)}}
12 | {{yield}}
13 | {{else}}
14 | {{#if @label}}
15 | {{@label}}
16 | {{else}}
17 |
18 | {{/if}}
19 | {{/if}}
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/components/sy-checkmark/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 |
3 | export default class SyCheckmark extends Component {
4 | get icon() {
5 | return this.args.checked ? "check-square" : "square";
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/components/sy-checkmark/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/components/sy-datepicker-btn/component.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-components
4 | * @public
5 | */
6 |
7 | import { action } from "@ember/object";
8 | import SyDatepickerComponent from "timed/components/sy-datepicker/component";
9 | import { localCopy } from "tracked-toolbox";
10 |
11 | /**
12 | * The sy datepicker btn component
13 | *
14 | * @class SyDatepickerBtnComponent
15 | * @extends SyDatepickerComponent
16 | * @public
17 | */
18 | export default class SyDatepickerBtnComponent extends SyDatepickerComponent {
19 | @localCopy("args.current") center;
20 |
21 | @action
22 | updateCenter({ moment }) {
23 | this.center = moment;
24 | }
25 |
26 | @action
27 | updateSelection({ moment }) {
28 | this.args.onChange(moment);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/components/sy-datepicker-btn/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/components/sy-durationpicker-day/component.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import SyDurationpickerComponent from "timed/components/sy-durationpicker/component";
3 |
4 | export default class SyDurationpickerDayComponent extends SyDurationpickerComponent {
5 | maxlength = 5;
6 |
7 | max = moment.duration({ h: 24, m: 0 });
8 |
9 | min = moment.duration({ h: 0, m: 0 });
10 |
11 | sanitize(value) {
12 | return value.replace(/[^\d:]/, "");
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/components/sy-durationpicker-day/template.hbs:
--------------------------------------------------------------------------------
1 |
18 |
--------------------------------------------------------------------------------
/app/components/sy-modal-target/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/components/sy-modal/body/styles.scss:
--------------------------------------------------------------------------------
1 | & {
2 | overflow-x: hidden;
3 |
4 | > .form-group:last-child {
5 | margin-bottom: 0;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/app/components/sy-modal/body/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{yield}}
3 |
4 |
--------------------------------------------------------------------------------
/app/components/sy-modal/footer/template.hbs:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/app/components/sy-modal/header/template.hbs:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/components/sy-modal/overlay/component.js:
--------------------------------------------------------------------------------
1 | import { registerDestructor } from "@ember/destroyable";
2 | import { action } from "@ember/object";
3 | import Component from "@glimmer/component";
4 |
5 | export default class SyModalOverlay extends Component {
6 | @action
7 | setupClickHandler(element) {
8 | this.element = element;
9 |
10 | element.addEventListener("click", this.handleClick);
11 |
12 | registerDestructor(this, () => {
13 | element.removeEventListener("click", this.handleClick);
14 | });
15 | }
16 |
17 | @action
18 | handleClick(e) {
19 | if (e.target === this.element) {
20 | this.args.onClose();
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/components/sy-modal/overlay/template.hbs:
--------------------------------------------------------------------------------
1 |
5 | {{yield}}
6 |
7 |
--------------------------------------------------------------------------------
/app/components/sy-modal/template.hbs:
--------------------------------------------------------------------------------
1 | {{#if @visible}}
2 |
3 |
4 |
5 | {{yield
6 | (hash
7 | header=(component "sy-modal/header" close=(optional @onClose))
8 | body=(component "sy-modal/body" close=(optional @onClose))
9 | footer=(component "sy-modal/footer" close=(optional @onClose))
10 | close=(optional @onClose)
11 | )
12 | }}
13 |
14 |
15 |
16 | {{/if}}
17 |
--------------------------------------------------------------------------------
/app/components/sy-timepicker/template.hbs:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/app/components/sy-toggle/component.js:
--------------------------------------------------------------------------------
1 | import { assert } from "@ember/debug";
2 | import { action } from "@ember/object";
3 | import Component from "@glimmer/component";
4 |
5 | export default class SyToggle extends Component {
6 | constructor(...args) {
7 | super(...args);
8 |
9 | assert("You must pass a onToggle callback.", this.args.onToggle);
10 | }
11 |
12 | @action
13 | handleKeyUp(event) {
14 | // only trigger on "Space" key
15 | if (event.keyCode === 32 && !this.args.disabled) {
16 | this.args.onToggle();
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/components/sy-toggle/template.hbs:
--------------------------------------------------------------------------------
1 |
10 | {{#if (has-block)}}
11 | {{yield}}
12 | {{else}}
13 |
14 | {{/if}}
15 |
16 |
--------------------------------------------------------------------------------
/app/components/sy-topnav/component.js:
--------------------------------------------------------------------------------
1 | import { inject as service } from "@ember/service";
2 | import Component from "@glimmer/component";
3 | import { tracked } from "@glimmer/tracking";
4 |
5 | export default class SyTopnav extends Component {
6 | @service session;
7 |
8 | @service media;
9 |
10 | @tracked expand = false;
11 |
12 | get navMobile() {
13 | return this.media.isMo || this.media.isXs || this.media.isSm;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/components/timed-clock/component.js:
--------------------------------------------------------------------------------
1 | import { setProperties } from "@ember/object";
2 | import { isTesting, macroCondition } from "@embroider/macros";
3 | import Component from "@glimmer/component";
4 | import { tracked } from "@glimmer/tracking";
5 | import { task, timeout } from "ember-concurrency";
6 | import moment from "moment";
7 |
8 | export default class TimedClock extends Component {
9 | @tracked hour = 0;
10 | @tracked minute = 0;
11 | @tracked second = 0;
12 |
13 | _update() {
14 | const now = moment();
15 |
16 | const second = now.seconds() * 6;
17 | const minute = now.minutes() * 6 + second / 60;
18 | const hour = ((now.hours() % 12) / 12) * 360 + minute / 12;
19 |
20 | setProperties(this, { second, minute, hour });
21 | }
22 |
23 | @task
24 | *timer() {
25 | for (;;) {
26 | this._update();
27 |
28 | /* istanbul ignore else */
29 | if (macroCondition(isTesting())) {
30 | return;
31 | }
32 |
33 | /* istanbul ignore next */
34 | yield timeout(1000);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/components/timed-clock/template.hbs:
--------------------------------------------------------------------------------
1 |
9 |
10 |
20 |
30 |
40 |
41 |
--------------------------------------------------------------------------------
/app/components/tracking-bar/component.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-components
4 | * @public
5 | */
6 | import { inject as service } from "@ember/service";
7 | import Component from "@glimmer/component";
8 |
9 | /**
10 | * The tracking bar component
11 | *
12 | * @class TrackingBarComponent
13 | * @extends Ember.Component
14 | * @public
15 | */
16 | export default class TrackingBar extends Component {
17 | @service tracking;
18 | }
19 |
--------------------------------------------------------------------------------
/app/components/user-selection/template.hbs:
--------------------------------------------------------------------------------
1 | {{yield
2 | (hash
3 | user=(component
4 | (ensure-safe-component "optimized-power-select")
5 | options=this.users
6 | disabled=@disabled
7 | selected=@user
8 | placeholder="Select user..."
9 | searchField="longName"
10 | tagName="div"
11 | class="user-select"
12 | allowClear=true
13 | onChange=@onChange
14 | extra=(hash
15 | lazy=true
16 | selectedTemplate=this.selectedTemplate
17 | optionTemplate=this.optionTemplate
18 | )
19 | )
20 | )
21 | }}
22 |
--------------------------------------------------------------------------------
/app/components/vertical-collection/component.js:
--------------------------------------------------------------------------------
1 | import { isTesting, macroCondition } from "@embroider/macros";
2 | import VerticalCollectionComponent from "@html-next/vertical-collection/components/vertical-collection/component";
3 | import classic from "ember-classic-decorator";
4 |
5 | @classic
6 | export default class VerticalCollection extends VerticalCollectionComponent {
7 | init(...args) {
8 | super.init(...args);
9 |
10 | if (macroCondition(isTesting())) {
11 | this.renderAll = true;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/components/weekly-overview-benchmark/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 |
3 | export default class WeeklyOverviewBenchmark extends Component {
4 | /**
5 | * Maximum worktime
6 | *
7 | * This is 'only' 20h since noone works 24h a day..
8 | *
9 | * @property {Number} max
10 | * @public
11 | */
12 | get max() {
13 | return this.args.max || 20;
14 | }
15 |
16 | /**
17 | * The offset to the bottom
18 | *
19 | * @property {String} style
20 | * @public
21 | */
22 | get style() {
23 | return { bottom: `calc(100% / ${this.max} * ${this.args.hours})` };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/components/weekly-overview-benchmark/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if @showLabel}}
3 | {{@hours}}h
4 | {{/if}}
5 |
6 |
--------------------------------------------------------------------------------
/app/components/weekly-overview-day/component.js:
--------------------------------------------------------------------------------
1 | import { action } from "@ember/object";
2 | import Component from "@glimmer/component";
3 | import { tracked } from "@glimmer/tracking";
4 | export default class WeeklyOverviewDay extends Component {
5 | /**
6 | * Maximum worktime in hours
7 | *
8 | * @property {Number} max
9 | * @public
10 | */
11 | @tracked max = 20;
12 |
13 | get title() {
14 | const pre = this.args.prefix?.length ? `${this.args.prefix}, ` : "";
15 |
16 | let title = `${this.args.worktime.hours()}h`;
17 |
18 | if (this.args.worktime.minutes()) {
19 | title += ` ${this.args.worktime.minutes()}m`;
20 | }
21 | return `${pre}${title}`;
22 | }
23 |
24 | get style() {
25 | const height = Math.min(
26 | (this.args.worktime.asHours() / this.max) * 100,
27 | 100
28 | );
29 | return { height: `${height}%` };
30 | }
31 |
32 | @action
33 | click(event) {
34 | const action = this.args.onClick;
35 |
36 | if (action) {
37 | event.preventDefault();
38 |
39 | this.args.onClick(this.args.day);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/components/weekly-overview-day/template.hbs:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 | {{moment-format @day 'DD'}}
16 | {{moment-format @day 'dd'}}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/components/weekly-overview/component.js:
--------------------------------------------------------------------------------
1 | import Component from "@glimmer/component";
2 | import { tracked } from "@glimmer/tracking";
3 |
4 | export default class WeeklyOverview extends Component {
5 | @tracked height = 150;
6 |
7 | get hours() {
8 | return this.args.expected.asHours();
9 | }
10 |
11 | get style() {
12 | return { height: `${this.height}px` };
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/components/weekly-overview/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{yield}}
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/components/welcome-modal/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Welcome to Timed!
5 |
6 |
Would you like to take a tour?
7 |
8 |
9 |
10 |
11 |
17 | Never
18 |
19 |
20 |
26 | Later
27 |
28 |
29 |
35 | Sure
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/app/components/worktime-balance-chart/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/controllers/qpcontroller.js:
--------------------------------------------------------------------------------
1 | import Controller from "@ember/controller";
2 | import { next } from "@ember/runloop";
3 |
4 | export default class ControllersQPControllerController extends Controller {
5 | #defaults = {};
6 |
7 | constructor(...args) {
8 | super(...args);
9 |
10 | // defer until the extending controller has set it's query params
11 | next(() => this.storeQPDefaults());
12 | }
13 |
14 | storeQPDefaults() {
15 | this.queryParams.forEach((qp) => {
16 | this.#defaults[qp] = this[qp];
17 | });
18 | }
19 |
20 | resetQueryParams(options = { except: [] }) {
21 | this.queryParams.forEach((qp) => {
22 | if (!options.except.includes(qp)) {
23 | this[qp] = this.#defaults[qp];
24 | }
25 | });
26 | }
27 |
28 | get allQueryParams() {
29 | return this.queryParams.reduce(
30 | (acc, key) =>
31 | Object.defineProperty(acc, key, {
32 | value: this[key],
33 | enumerable: true,
34 | }),
35 | {}
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/helpers/balance-highlight-class.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-helpers
4 | * @public
5 | */
6 | import { helper } from "@ember/component/helper";
7 | import moment from "moment";
8 |
9 | /**
10 | * Helper to determine the color of a balance
11 | *
12 | * > 0: red-ish
13 | * < 0: green-ish
14 | *
15 | * @function balanceHighlightClass
16 | * @param {Array} options The options delivered to the helper
17 | * @return {String} The CSS class to apply the color
18 | * @public
19 | */
20 | export function balanceHighlightClass([balance]) {
21 | const minutes = moment.isDuration(balance) ? balance.asMinutes() : 0;
22 |
23 | if (minutes > 0) {
24 | return "color-success";
25 | } else if (minutes < 0) {
26 | return "color-danger";
27 | }
28 |
29 | return "";
30 | }
31 |
32 | export default helper(balanceHighlightClass);
33 |
--------------------------------------------------------------------------------
/app/helpers/format-duration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-helpers
4 | * @public
5 | */
6 | import { helper } from "@ember/component/helper";
7 | import formatDuration from "timed/utils/format-duration";
8 |
9 | /**
10 | * The format duration helper
11 | *
12 | * @function formatDurationFn
13 | * @param {Array} args The arguments delivered to the helper
14 | * @return {String} The formatted duration
15 | * @public
16 | */
17 | export const formatDurationFn = (args) => formatDuration(...args);
18 |
19 | export default helper(formatDurationFn);
20 |
--------------------------------------------------------------------------------
/app/helpers/humanize-duration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-helpers
4 | * @public
5 | */
6 | import { helper } from "@ember/component/helper";
7 | import humanizeDuration from "timed/utils/humanize-duration";
8 |
9 | /**
10 | * The humanize duration helper
11 | *
12 | * @function humanizeDurationFn
13 | * @param {Array} args The arguments delivered to the helper
14 | * @return {String} The humanized duration
15 | * @public
16 | */
17 | export const humanizeDurationFn = (args) => humanizeDuration(...args);
18 |
19 | export default helper(humanizeDurationFn);
20 |
--------------------------------------------------------------------------------
/app/helpers/parse-django-duration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-helpers
4 | * @public
5 | */
6 | import { helper } from "@ember/component/helper";
7 | import parseDjangoDuration from "timed/utils/parse-django-duration";
8 |
9 | /**
10 | * The parse django duration helper
11 | *
12 | * @function parseDjangoDurationFn
13 | * @param {Array} args The arguments delivered to the helper
14 | * @return {moment.duration} The moment duration
15 | * @public
16 | */
17 | export const parseDjangoDurationFn = (args) => parseDjangoDuration(...args);
18 |
19 | export default helper(parseDjangoDurationFn);
20 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Timed
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{content-for "head"}}
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 |
19 |
20 | {{content-for "body"}}
21 |
22 |
23 |
24 |
25 | {{content-for "body-footer"}}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/index/activities/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class IndexActivitiesRoute extends Route {
4 | model() {
5 | return this.modelFor("index");
6 | }
7 |
8 | setupController(controller, ...args) {
9 | super.setupController(controller, ...args);
10 |
11 | controller.set("user", this.modelFor("protected"));
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/index/attendances/route.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-routes
4 | * @public
5 | */
6 | import Route from "@ember/routing/route";
7 |
8 | /**
9 | * The index attendances route
10 | *
11 | * @class IndexAttendancesRoute
12 | * @extends Ember.Route
13 | * @public
14 | */
15 | export default class AttendaceIndexRoute extends Route {
16 | /**
17 | * Setup controller hook, set the current user
18 | *
19 | * @method setupContrller
20 | * @param {Ember.Controller} controller The controller
21 | * @public
22 | */
23 | setupController(controller, ...args) {
24 | super.setupController(controller, ...args);
25 |
26 | controller.set("user", this.modelFor("protected"));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/initializers/responsive.js:
--------------------------------------------------------------------------------
1 | import { initialize } from "ember-responsive/initializers/responsive";
2 |
3 | /**
4 | * Ember responsive initializer
5 | *
6 | * Supports auto injecting media service app-wide.
7 | *
8 | * Generated by the ember-responsive addon. Customize initialize to change
9 | * injection.
10 | */
11 |
12 | export default {
13 | name: "responsive",
14 | initialize,
15 | };
16 |
--------------------------------------------------------------------------------
/app/login/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 | /**
3 | * The login route
4 | *
5 | * @class LoginRoute
6 | * @extends Ember.Route
7 | * @public
8 | */
9 | export default class LoginRoute extends Route {}
10 |
--------------------------------------------------------------------------------
/app/login/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
Welcome to Timed
3 |
4 |
5 |
6 |
7 | Login via SSO
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/models/absence-balance.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo, hasMany } from "@ember-data/model";
2 |
3 | export default class AbsenceBalance extends Model {
4 | @attr("number") credit;
5 | @attr("number") usedDays;
6 | @attr("django-duration") usedDuration;
7 | @attr("number") balance;
8 | @belongsTo("user") user;
9 | @belongsTo("absence-type") absenceType;
10 | @hasMany("absence-credit") absenceCredits;
11 | }
12 |
--------------------------------------------------------------------------------
/app/models/absence-credit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-models
4 | * @public
5 | */
6 | import Model, { attr, belongsTo } from "@ember-data/model";
7 |
8 | /**
9 | * The absence credit model
10 | *
11 | * @class AbsenceCredit
12 | * @extends DS.Model
13 | * @public
14 | */
15 | export default class AbsenceCredit extends Model {
16 | /**
17 | * The days
18 | *
19 | * @property {Number} days
20 | * @public
21 | */
22 | @attr("number") days;
23 |
24 | /**
25 | * The date
26 | *
27 | * @property {moment} date
28 | * @public
29 | */
30 | @attr("django-date") date;
31 |
32 | /**
33 | * The comment
34 | *
35 | * @property {String} comment
36 | * @public
37 | */
38 | @attr("string", { defaultValue: "" }) comment;
39 |
40 | /**
41 | * The absence type for which this credit counts
42 | *
43 | * @property {AbsenceType} absenceType
44 | * @public
45 | */
46 | @belongsTo("absence-type") absenceType;
47 |
48 | /**
49 | * The user to which this credit belongs to
50 | *
51 | * @property {User} user
52 | * @public
53 | */
54 | @belongsTo("user") user;
55 | }
56 |
--------------------------------------------------------------------------------
/app/models/absence-type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-models
4 | * @public
5 | */
6 | import Model, { attr, hasMany } from "@ember-data/model";
7 |
8 | /**
9 | * The absence type model
10 | *
11 | * @class AbsenceType
12 | * @extends DS.Model
13 | * @public
14 | */
15 | export default class AbsenceType extends Model {
16 | /**
17 | * The name of the absence type
18 | *
19 | * E.g Military, Holiday or Sickness
20 | *
21 | * @property {String} name
22 | * @public
23 | */
24 | @attr("string") name;
25 |
26 | /**
27 | * Whether the absence type only fills the worktime
28 | *
29 | * @property {Boolean} fillWorktime
30 | * @public
31 | */
32 | @attr("boolean") fillWorktime;
33 |
34 | /**
35 | * The balances for this type
36 | *
37 | * @property {AbsenceBalance[]} absenceBalances
38 | * @public
39 | */
40 | @hasMany("absence-balance") absenceBalances;
41 | }
42 |
--------------------------------------------------------------------------------
/app/models/billing-type.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-models
4 | * @public
5 | */
6 | import Model, { attr } from "@ember-data/model";
7 |
8 | /**
9 | * The billing type model
10 | *
11 | * @class BillingType
12 | * @extends DS.Model
13 | * @public
14 | */
15 | export default class BillingType extends Model {
16 | /**
17 | * The name
18 | *
19 | * @property {String} name
20 | * @public
21 | */
22 | @attr("string") name;
23 | }
24 |
--------------------------------------------------------------------------------
/app/models/cost-center.js:
--------------------------------------------------------------------------------
1 | import Model, { attr } from "@ember-data/model";
2 |
3 | export default class CostCenter extends Model {
4 | @attr("string") name;
5 | @attr("string") reference;
6 | }
7 |
--------------------------------------------------------------------------------
/app/models/customer-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr } from "@ember-data/model";
2 |
3 | export default class CustomerStatistics extends Model {
4 | @attr("django-duration") duration;
5 | @attr name;
6 | }
7 |
--------------------------------------------------------------------------------
/app/models/location.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-models
4 | * @public
5 | */
6 | import Model, { attr } from "@ember-data/model";
7 |
8 | /**
9 | * The location model
10 | *
11 | * @class Location
12 | * @extends DS.Model
13 | * @public
14 | */
15 | export default class Location extends Model {
16 | /**
17 | * The name
18 | *
19 | * @property {String} name
20 | * @public
21 | */
22 | @attr("string") name;
23 |
24 | /**
25 | * The days on which users in this location need to work
26 | *
27 | * @property {Number[]} workdays
28 | * @public
29 | */
30 | @attr("django-workdays") workdays;
31 | }
32 |
--------------------------------------------------------------------------------
/app/models/month-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr } from "@ember-data/model";
2 |
3 | export default class MonthStatistic extends Model {
4 | @attr("number") year;
5 | @attr("number") month;
6 | @attr("django-duration") duration;
7 | }
8 |
--------------------------------------------------------------------------------
/app/models/overtime-credit.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class OvertimeCredit extends Model {
4 | @attr("django-date") date;
5 | @attr("django-duration") duration;
6 | @attr("string", { defaultValue: "" }) comment;
7 | @belongsTo("user") user;
8 | }
9 |
--------------------------------------------------------------------------------
/app/models/project-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class ProjectStatistics extends Model {
4 | @attr name;
5 | @attr("django-duration") estimatedTime;
6 | @attr("django-duration") duration;
7 | @attr("django-duration") totalRemainingEffort;
8 | @belongsTo("customer") customer;
9 | }
10 |
--------------------------------------------------------------------------------
/app/models/public-holiday.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-models
4 | * @public
5 | */
6 | import Model, { attr, belongsTo } from "@ember-data/model";
7 |
8 | /**
9 | * The public holiday model
10 | *
11 | * @class PublicHoliday
12 | * @extends DS.Model
13 | * @public
14 | */
15 | export default class PublicHoliday extends Model {
16 | /**
17 | * The name
18 | *
19 | * @property {String} name
20 | * @public
21 | */
22 | @attr("string") name;
23 |
24 | /**
25 | * The date
26 | *
27 | * @property {moment} date
28 | * @public
29 | */
30 | @attr("django-date") date;
31 |
32 | /**
33 | * The location
34 | *
35 | * @property {Location} location
36 | * @public
37 | */
38 | @belongsTo("location") location;
39 | }
40 |
--------------------------------------------------------------------------------
/app/models/report-intersection.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class ReportIntersection extends Model {
4 | @attr("string") comment;
5 | @attr("boolean", { allowNull: true, defaultValue: null }) notBillable;
6 | @attr("boolean", { allowNull: true, defaultValue: false }) rejected;
7 | @attr("boolean", { allowNull: true, defaultValue: null }) review;
8 | @attr("boolean", { allowNull: true, defaultValue: null }) billed;
9 | @attr("boolean", { allowNull: true, defaultValue: null }) verified;
10 |
11 | @belongsTo("customer") customer;
12 | @belongsTo("project") project;
13 | @belongsTo("task") task;
14 | @belongsTo("user") user;
15 | }
16 |
--------------------------------------------------------------------------------
/app/models/task-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class TaskStatistics extends Model {
4 | @attr name;
5 | @attr("django-duration") duration;
6 | @attr("django-duration") estimatedTime;
7 | @attr("django-duration") mostRecentRemainingEffort;
8 | @belongsTo("project") project;
9 | }
10 |
--------------------------------------------------------------------------------
/app/models/task.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo, hasMany } from "@ember-data/model";
2 |
3 | export default class Task extends Model {
4 | @attr("string", { defaultValue: "" }) name;
5 | @attr("django-duration") estimatedTime;
6 | @attr("django-duration") mostRecentRemainingEffort;
7 | @attr("boolean", { defaultValue: false }) archived;
8 | @attr("string", { defaultValue: "" }) reference;
9 |
10 | @belongsTo("project") project;
11 | @hasMany("task-assignee") assignees;
12 |
13 | /**
14 | * Flag saying that this is a task.
15 | * Used in /app/customer-suggestion/template.hbs
16 | * We're using this as a workaround for the fact that one
17 | * can't seem to use helpers like "(eq" in inline templates
18 | *
19 | * @property project
20 | * @type {Project}
21 | * @public
22 | */
23 | isTask = true;
24 |
25 | get longName() {
26 | const taskName = this.name;
27 | const projectName = this.project.get("name");
28 | const customerName = this.project.get("customer.name");
29 |
30 | return `${customerName} > ${projectName} > ${taskName}`;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/models/user-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class UserStatistic extends Model {
4 | @attr("django-duration") duration;
5 |
6 | @belongsTo("user") user;
7 | }
8 |
--------------------------------------------------------------------------------
/app/models/worktime-balance.js:
--------------------------------------------------------------------------------
1 | import Model, { attr, belongsTo } from "@ember-data/model";
2 |
3 | export default class WorktimeBalance extends Model {
4 | @attr("django-date") date;
5 | @attr("django-duration") balance;
6 | @belongsTo("user") user;
7 | }
8 |
--------------------------------------------------------------------------------
/app/models/year-statistic.js:
--------------------------------------------------------------------------------
1 | import Model, { attr } from "@ember-data/model";
2 |
3 | export default class YearStatistic extends Model {
4 | @attr("number") year;
5 | @attr("django-duration") duration;
6 | }
7 |
--------------------------------------------------------------------------------
/app/no-access/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class NoAccessRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/no-access/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
You do not have permission to access Timed.
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/notfound/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class NotFoundRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/notfound/template.hbs:
--------------------------------------------------------------------------------
1 |
2 |
404
3 | The site you requested does not exist
4 |
5 |
--------------------------------------------------------------------------------
/app/projects/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class ProjectsRoute extends Route {
4 | setupController(controller, ...args) {
5 | super.setupController(controller, ...args);
6 |
7 | controller.fetchProjectsByUser.perform();
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/protected/template.hbs:
--------------------------------------------------------------------------------
1 |
2 | {{#if this.loading}} {{/if}}
3 |
4 |
5 |
6 | {{outlet}}
7 |
8 |
9 |
10 |
16 |
--------------------------------------------------------------------------------
/app/serializers/application.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-serializers
4 | * @public
5 | */
6 | import JSONAPISerializer from "@ember-data/serializer/json-api";
7 |
8 | /**
9 | * The application serializer
10 | *
11 | * @class ApplicationSerializer
12 | * @extends DS.JSONAPISerializer
13 | * @public
14 | */
15 | export default class ApplicationSerializer extends JSONAPISerializer {}
16 |
--------------------------------------------------------------------------------
/app/serializers/attendance.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-serializers
4 | * @public
5 | */
6 | import ApplicationSerializer from "timed/serializers/application";
7 |
8 | /**
9 | * The attendance serializer
10 | *
11 | * @class AttendanceSerializer
12 | * @extends ApplicationSerializer
13 | * @public
14 | */
15 | export default ApplicationSerializer.extend({
16 | /**
17 | * The attribute mapping
18 | *
19 | * This mapps some properties of the response to another
20 | * property name of the model
21 | *
22 | * @property {Object} attrs
23 | * @property {String} from
24 | * @property {String} to
25 | * @public
26 | */
27 | attrs: {
28 | from: "from-time",
29 | to: "to-time",
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/app/serializers/employment.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-serializers
4 | * @public
5 | */
6 | import ApplicationSerializer from "timed/serializers/application";
7 |
8 | /**
9 | * The employment block serializer
10 | *
11 | * @class EmploymentBlockSerializer
12 | * @extends ApplicationSerializer
13 | * @public
14 | */
15 | export default ApplicationSerializer.extend({
16 | /**
17 | * The attribute mapping
18 | *
19 | * This mapps some properties of the response to another
20 | * property name of the model
21 | *
22 | * @property {Object} attrs
23 | * @property {String} start
24 | * @property {String} end
25 | * @public
26 | */
27 | attrs: {
28 | start: "start-date",
29 | end: "end-date",
30 | },
31 | });
32 |
--------------------------------------------------------------------------------
/app/services/autostart-tour.js:
--------------------------------------------------------------------------------
1 | import Service from "@ember/service";
2 | import { tracked } from "@glimmer/tracking";
3 | import TOURS from "timed/tours";
4 |
5 | /**
6 | * Autostart tour service
7 | *
8 | * This service helps connecting the tours to the localstorage
9 | *
10 | * @class AutostartTourService
11 | * @extends Ember.Service
12 | * @public
13 | */
14 | export default class AutostartTourService extends Service {
15 | tours = Object.keys(TOURS);
16 | /**
17 | * The item key to use in the localstorage
18 | *
19 | * @property {String} doneKey
20 | * @public
21 | */
22 | @tracked doneKey = "timed-tour";
23 |
24 | get done() {
25 | return Array.from(JSON.parse(localStorage.getItem(this.doneKey)) || []);
26 | }
27 |
28 | set done(value = []) {
29 | localStorage.setItem(this.doneKey, JSON.stringify(value));
30 | }
31 |
32 | get undoneTours() {
33 | return this.tours.filter((tour) => !this.done.includes(tour));
34 | }
35 |
36 | get allDone() {
37 | return this.undoneTours.length === 0;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/sso-login/route.js:
--------------------------------------------------------------------------------
1 | import OIDCAuthenticationRoute from "ember-simple-auth-oidc/routes/oidc-authentication";
2 |
3 | export default class SsoLoginRoute extends OIDCAuthenticationRoute {}
4 |
--------------------------------------------------------------------------------
/app/statistics/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class StatisticsRoute extends Route {
4 | setupController(controller) {
5 | controller.data.perform();
6 | controller.prefetchData.perform();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/app/styles/attendances.scss:
--------------------------------------------------------------------------------
1 | .table--attendances > tbody > tr > td {
2 | &:nth-child(1),
3 | &:nth-child(2) {
4 | width: auto;
5 | }
6 |
7 | &:nth-child(3) {
8 | width: 105px;
9 | text-align: right;
10 |
11 | .btn {
12 | display: inline-flex;
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/styles/badge.scss:
--------------------------------------------------------------------------------
1 | .badge {
2 | display: inline-block;
3 | min-width: 10px;
4 | padding: 0.2rem 0.4rem;
5 | margin-left: 0.2rem;
6 | font-size: $font-size-base * 0.8;
7 | line-height: 1;
8 | color: rgb(255,255,255);
9 | text-align: center;
10 | white-space: nowrap;
11 | vertical-align: middle;
12 | background-color: $color-secondary;
13 | border-radius: 10px;
14 | }
15 |
16 | a.active .badge,
17 | .badge--primary {
18 | background-color: $color-primary;
19 | }
20 |
21 | .badge--success {
22 | background-color: $color-success;
23 | }
24 |
25 | .badge--info {
26 | background-color: $color-info;
27 | }
28 |
29 | .badge--warning {
30 | background-color: $color-warning;
31 | }
32 |
33 | .badge--danger {
34 | background-color: $color-danger;
35 | }
36 |
--------------------------------------------------------------------------------
/app/styles/components/date-buttons.scss:
--------------------------------------------------------------------------------
1 | .date-button {
2 | width: 32.5%;
3 | }
4 |
--------------------------------------------------------------------------------
/app/styles/components/date-navigation.scss:
--------------------------------------------------------------------------------
1 | .date-navigation {
2 | display: flex;
3 | flex-grow: 1;
4 | justify-content: space-between;
5 | padding: 1rem 0;
6 | }
7 |
8 | .btn-group {
9 | margin-right: 10px;
10 | }
11 |
12 | .date-navigation-container {
13 | flex-grow: 1;
14 | display: flex;
15 | }
16 |
17 | @media #{$sm-viewport} {
18 | .date-navigation {
19 | justify-content: flex-end;
20 | padding: 0;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/styles/components/filter-sidebar--label.scss:
--------------------------------------------------------------------------------
1 | .filter-sidebar-label {
2 | display: block;
3 | font-size: 0.75rem;
4 | font-weight: 500;
5 | padding: 0.5rem 0;
6 | }
7 |
8 | .filter-sidebar-label > * {
9 | font-weight: 300;
10 | margin-top: 0.3rem;
11 | }
12 |
--------------------------------------------------------------------------------
/app/styles/components/magic-link-btn.scss:
--------------------------------------------------------------------------------
1 | .ember-basic-dropdown-content {
2 | z-index: 1002;
3 | }
4 |
5 | .magic-link-modal {
6 | min-width: 500px;
7 | }
8 |
--------------------------------------------------------------------------------
/app/styles/components/nav-top.scss:
--------------------------------------------------------------------------------
1 | nav {
2 | .nav-top-header-title {
3 | display: none;
4 | font-weight: 500;
5 | }
6 |
7 | .timed-clock {
8 | padding: 0.3rem 0;
9 | margin-right: 0.3rem;
10 | }
11 |
12 | .nav-top-toggle {
13 | display: block;
14 | }
15 |
16 | @media #{$nav-top-mobile-width} {
17 | .nav-top-header-title {
18 | display: block;
19 | }
20 |
21 | .nav-top-header-title-version {
22 | color: rgb(150, 150, 150);
23 | font-size: 0.5rem;
24 | font-family: $font-family-mono;
25 | }
26 |
27 | .nav-top-list-item a.active {
28 | background-color: $color-primary;
29 | color: #fff;
30 | }
31 |
32 | .nav-top-list-item a:hover {
33 | background-color: lighten($color-primary, 20%);
34 | color: #fff;
35 | }
36 |
37 | .nav-top-list-item a {
38 | padding: 0.6rem 0.8rem;
39 | border-radius: 0;
40 | }
41 |
42 | .nav-top-toggle {
43 | display: none;
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/styles/components/scroll-container.scss:
--------------------------------------------------------------------------------
1 | .scroll-container {
2 | max-height: 100%;
3 | min-height: 20px;
4 | overflow-y: auto;
5 |
6 | &::after,
7 | &::before {
8 | content: "";
9 | position: absolute;
10 | z-index: 1;
11 | left: 0;
12 | right: 0;
13 | height: 7px;
14 | background: linear-gradient(
15 | to bottom,
16 | rgba(0, 0, 0, 0.03),
17 | rgba(0, 0, 0, 0)
18 | );
19 | }
20 |
21 | &::before {
22 | top: 0;
23 | }
24 |
25 | &::after {
26 | bottom: 0;
27 | transform: rotate(180deg);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/styles/components/sort-header.scss:
--------------------------------------------------------------------------------
1 | .sort-header {
2 | white-space: nowrap;
3 | cursor: pointer;
4 | }
5 |
--------------------------------------------------------------------------------
/app/styles/components/sy-calendar.scss:
--------------------------------------------------------------------------------
1 | .sy-calendar {
2 | @include ember-power-calendar($cell-size: 35px);
3 |
4 | .ember-power-calendar-nav-control {
5 | color: $color-primary;
6 | cursor: pointer;
7 | }
8 |
9 | .nav-select-month,
10 | .nav-select-year {
11 | position: relative;
12 |
13 | select {
14 | position: absolute;
15 | top: 0;
16 | left: 0;
17 | right: 0;
18 | bottom: 0;
19 | opacity: 0;
20 | }
21 | }
22 |
23 | .ember-power-calendar-day {
24 | cursor: pointer;
25 | transition: background-color 300ms ease, color 300ms ease;
26 |
27 | &--focused {
28 | box-shadow: inset 0 -2px 0 0 $color-primary;
29 | }
30 |
31 | &--selected {
32 | color: rgb(255, 255, 255);
33 | background-color: lighten($color-primary, 20%);
34 |
35 | &:hover {
36 | color: rgb(255, 255, 255);
37 | background-color: lighten($color-primary, 30%);
38 | }
39 | }
40 | }
41 | }
42 |
43 | .sy-calendar.sy-datepicker {
44 | border: 1px solid $color-border;
45 | padding: 0.5rem;
46 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2);
47 | }
48 |
--------------------------------------------------------------------------------
/app/styles/components/sy-checkbox.scss:
--------------------------------------------------------------------------------
1 | .sy-checkbox > input[type="checkbox"]:indeterminate + label:after {
2 | content: "\2012";
3 | opacity: 1;
4 | transform: scale(1);
5 | left: 0.23rem;
6 | }
7 |
--------------------------------------------------------------------------------
/app/styles/components/sy-datepicker.scss:
--------------------------------------------------------------------------------
1 | .sy-datepicker-trigger.ember-basic-dropdown-trigger {
2 | position: relative;
3 |
4 | span.clear {
5 | cursor: pointer;
6 | color: #555;
7 | line-height: 1.5;
8 | font-size: 0.9rem;
9 | background-color: #fff;
10 | padding: 0 2px;
11 | right: 0.75rem;
12 | top: 50%;
13 | transform: translateY(-50%);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/styles/components/sy-durationpicker-day.scss:
--------------------------------------------------------------------------------
1 | .extendend-durationpicker-day {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 |
6 | * {
7 | flex: 0 0 1.25rem;
8 | }
9 |
10 | input {
11 | width: 100%;
12 | flex: 1 0 70%;
13 | }
14 |
15 | :nth-child(2) {
16 | margin-left: 0.25rem;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/styles/components/sy-modal--footer.scss:
--------------------------------------------------------------------------------
1 | .modal-footer {
2 | .btn:not(:last-of-type) {
3 | margin-right: 0.7rem;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/app/styles/components/sy-modal--overlay.scss:
--------------------------------------------------------------------------------
1 | .modal-overlay {
2 | transition: none;
3 | }
4 |
--------------------------------------------------------------------------------
/app/styles/components/sy-toggle.scss:
--------------------------------------------------------------------------------
1 | @use "sass:color";
2 |
3 | .sy-toggle {
4 | display: flex;
5 | align-items: center;
6 | cursor: pointer;
7 | margin: auto;
8 |
9 | &.active {
10 | color: color.adjust($color-primary, $lightness: -10%);
11 | }
12 |
13 | &.inactive {
14 | color: $color-secondary;
15 | }
16 |
17 | &.form-control {
18 | background-color: unset;
19 | border: unset;
20 | box-shadow: unset;
21 | }
22 | }
23 |
24 | .form-list-cell > .margin-small-right {
25 | margin-right: 0.5rem;
26 | }
27 |
--------------------------------------------------------------------------------
/app/styles/components/timed-clock.scss:
--------------------------------------------------------------------------------
1 | .timed-clock {
2 | --clock-size: 50px;
3 | --clock-color: rgb(87, 87, 87);
4 | --clock-color-secondary: rgb(217, 83, 79);
5 |
6 | width: var(--clock-size);
7 | height: var(--clock-size);
8 |
9 | .circle {
10 | fill: transparent;
11 | stroke: var(--clock-color);
12 | }
13 |
14 | .hour {
15 | stroke: var(--clock-color);
16 | }
17 |
18 | .minute {
19 | stroke: var(--clock-color);
20 | }
21 |
22 | .second {
23 | stroke: var(--clock-color-secondary);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/styles/components/tracking-bar.scss:
--------------------------------------------------------------------------------
1 | .tracking-bar {
2 | background: white;
3 | border-bottom: 1px solid rgb(220, 220, 220);
4 | padding-bottom: 1.5rem;
5 | margin-bottom: 1.5rem;
6 |
7 | @media #{$lg-viewport} {
8 | #task-form {
9 | display: flex;
10 | flex-direction: row;
11 |
12 | .form-group {
13 | margin-bottom: 0;
14 | }
15 |
16 | .form-group:not(:last-child) {
17 | margin-right: 0.5rem;
18 | }
19 |
20 | .form-group:not(:last-child) {
21 | flex-grow: 2;
22 | }
23 |
24 | .form-group:nth-last-child(2) {
25 | flex-grow: 3;
26 | }
27 | }
28 |
29 | .form-control {
30 | width: 100%;
31 | }
32 | }
33 |
34 | @media #{$xs-viewport} {
35 | .form-control {
36 | padding-right: 35px;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/styles/components/weekly-overview-benchmark.scss:
--------------------------------------------------------------------------------
1 | .weekly-overview-benchmark {
2 | display: flex;
3 | align-items: center;
4 | position: absolute;
5 | left: 0;
6 | bottom: 0;
7 | top: 0;
8 | right: 0;
9 |
10 | &.expected {
11 | hr {
12 | background-color: $color-primary;
13 | }
14 |
15 | span {
16 | color: $color-primary;
17 | }
18 | }
19 |
20 | hr {
21 | margin: 0;
22 | border: none;
23 | height: 1px;
24 | width: 100%;
25 | background-color: transparentize($color-primary, 0.7);
26 | position: absolute;
27 | left: 0;
28 | right: 0;
29 | }
30 |
31 | span {
32 | width: 30px;
33 | font-family: $font-family-mono;
34 | font-size: 12px;
35 | text-align: right;
36 | color: transparentize($color-primary, 0.7);
37 | transform: translateY(50%);
38 | position: absolute;
39 | left: -35px;
40 | right: 0;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/styles/components/weekly-overview.scss:
--------------------------------------------------------------------------------
1 | .weekly-overview {
2 | width: 100%;
3 | padding: 20px 0 50px 50px;
4 | overflow: hidden;
5 | display: none;
6 | display: flex;
7 |
8 | .weekly-overview-children {
9 | flex-grow: 1;
10 | position: relative;
11 | display: flex;
12 | justify-content: space-around;
13 | align-items: flex-end;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/styles/components/welcome-modal.scss:
--------------------------------------------------------------------------------
1 | .welcome-modal-body {
2 | .logo {
3 | max-width: 50%;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/app/styles/loader.scss:
--------------------------------------------------------------------------------
1 | .loader {
2 | height: 2px;
3 | width: 100%;
4 | position: absolute;
5 | top: 0;
6 | right: 0;
7 | left: 0;
8 | z-index: 9999;
9 | overflow: hidden;
10 | background-color: #ddd;
11 | }
12 | .loader:before {
13 | display: block;
14 | position: absolute;
15 | content: "";
16 | left: -200px;
17 | width: 200px;
18 | height: 4px;
19 | background-color: $color-primary;
20 | animation: loading 2s linear infinite;
21 | }
22 |
23 | @keyframes loading {
24 | from {
25 | left: -200px;
26 | width: 30%;
27 | }
28 | 50% {
29 | width: 30%;
30 | }
31 | 70% {
32 | width: 70%;
33 | }
34 | 80% {
35 | left: 50%;
36 | }
37 | 95% {
38 | left: 120%;
39 | }
40 | to {
41 | left: 100%;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/styles/login.scss:
--------------------------------------------------------------------------------
1 | .login {
2 | text-align: center;
3 |
4 | h1 {
5 | margin-top: 0.8rem;
6 | }
7 |
8 | .timed-clock {
9 | margin: 0 auto 0.5rem;
10 | }
11 |
12 | @media only screen and (max-width: 768px) {
13 | .btn-primary {
14 | margin-left: 0.5rem;
15 | margin-right: 0.5rem;
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/styles/statistics.scss:
--------------------------------------------------------------------------------
1 | .table--statistics td:last-child,
2 | .table--statistics th:last-child {
3 | width: 50%;
4 | }
--------------------------------------------------------------------------------
/app/styles/toolbar.scss:
--------------------------------------------------------------------------------
1 | .toolbar {
2 | display: flex;
3 | flex-direction: row;
4 | }
5 |
6 | .toolbar-content {
7 | display: flex;
8 | flex-grow: 1;
9 | justify-content: flex-start;
10 | margin-bottom: 1rem;
11 | }
12 |
13 | .toolbar-content--right {
14 | justify-content: flex-end;
15 | }
16 |
--------------------------------------------------------------------------------
/app/styles/users-navigation.scss:
--------------------------------------------------------------------------------
1 | .user-navigation {
2 | margin: 0 ($page-padding-h * -1);
3 | border-bottom: 1px solid $color-border;
4 | background-color: rgb(255,255,255);
5 | box-shadow: 0 2px 6px rgba(0,0,0,0.1);
6 | }
7 |
8 | .user-navigation > ul {
9 | list-style: none;
10 | display: flex;
11 | width: 100%;
12 | justify-content: center;
13 | }
14 |
15 | .user-navigation > ul > li {
16 | flex: 1 0 auto;
17 | display: flex;
18 | }
19 |
20 | .user-navigation > ul > li > a {
21 | flex-grow: 1;
22 | text-align: center;
23 | padding: 0.9rem 2rem;
24 | color: rgb(180,180,180);
25 | font-size: 1.1rem;
26 | }
27 |
28 | .user-navigation > ul > li > a.active {
29 | color: $color-primary;
30 | box-shadow: inset 0 -2px 0 $color-primary;
31 | }
32 |
33 | @media #{$md-viewport} {
34 | .user-navigation > ul > li {
35 | flex-grow: 0;
36 | }
37 | }
38 |
39 | @media #{$lg-viewport} {
40 | .user-navigation {
41 | margin: 0 ($page-padding-h * 1.25 * -1);
42 | }
43 | }
44 |
45 | @media #{$xl-viewport} {
46 | .user-navigation{
47 | margin: 0 ($page-padding-h * 1.5 * -1);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/tours/index.js:
--------------------------------------------------------------------------------
1 | import IndexActivities from "./index/activities";
2 | import IndexAttendances from "./index/attendances";
3 | import IndexReports from "./index/reports";
4 |
5 | export default {
6 | "index.activities": IndexActivities,
7 | "index.attendances": IndexAttendances,
8 | "index.reports": IndexReports,
9 | };
10 |
--------------------------------------------------------------------------------
/app/tours/index/attendances.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | id: "addAttendance",
4 | target: ".btn-toolbar .btn-success",
5 | placement: "left",
6 | title: "Add attendance",
7 | content: `
8 |
9 | Attendances represent time blocks in which you were at the workplace.
10 | They don't count as worktime but are a help for you to roughly guess the
11 | worktime you should have.
12 |
13 |
14 | To add a new attendance just click here.
15 |
16 | `,
17 | },
18 | {
19 | id: "editAttendance",
20 | target: ".visible-md",
21 | placement: "top",
22 | title: "Edit attendance",
23 | content: `
24 |
25 | Now you can just adjust the time block by grabing and moving it or
26 | grabing it on one of the ends and adjusting the start or end time. The
27 | attendance saves automatically after every change.
28 |
29 |
30 | You can add as many attendances per day as you want.
31 |
32 | `,
33 | },
34 | ];
35 |
--------------------------------------------------------------------------------
/app/transforms/django-date.js:
--------------------------------------------------------------------------------
1 | import MomentTransform from "timed/transforms/moment";
2 |
3 | /**
4 | * The django date transform
5 | *
6 | * This transforms a django date into a moment date
7 | *
8 | * @class DjangoDateTransform
9 | * @extends MomentTransform
10 | * @public
11 | */
12 | export default class DjangoDateTransform extends MomentTransform {
13 | /**
14 | * The date format
15 | *
16 | * @property {String} format
17 | * @public
18 | */
19 | format = "YYYY-MM-DD";
20 | }
21 |
--------------------------------------------------------------------------------
/app/transforms/django-datetime.js:
--------------------------------------------------------------------------------
1 | import MomentTransform from "timed/transforms/moment";
2 |
3 | /**
4 | * The django datetime transform
5 | *
6 | * This transforms a django datetime into a moment datetime
7 | *
8 | * @class DjangoDatetimeTransform
9 | * @extends MomentTransform
10 | * @public
11 | */
12 | export default class DjangoDatetimeTransform extends MomentTransform {
13 | /**
14 | * The date format
15 | *
16 | * @property {String} format
17 | * @public
18 | */
19 | format = "YYYY-MM-DDTHH:mm:ss.SSSSZ";
20 | }
21 |
--------------------------------------------------------------------------------
/app/transforms/django-time.js:
--------------------------------------------------------------------------------
1 | import MomentTransform from "timed/transforms/moment";
2 |
3 | /**
4 | * The django time transform
5 | *
6 | * This transforms a django time into a moment object
7 | *
8 | * @class DjangoTimeTransform
9 | * @extends MomentTransform
10 | * @public
11 | */
12 | export default class DjangoTimeTransform extends MomentTransform {
13 | /**
14 | * The time format
15 | *
16 | * @property {String} format
17 | * @public
18 | */
19 | format = "HH:mm:ss";
20 | }
21 |
--------------------------------------------------------------------------------
/app/transforms/django-workdays.js:
--------------------------------------------------------------------------------
1 | import Transform from "@ember-data/serializer/transform";
2 |
3 | /**
4 | * Django worktime transform
5 | *
6 | * This transforms a string like '1,2,3' into an array of numbers
7 | *
8 | * @class DjangoWorktimeTransform
9 | * @extends DS.Transform
10 | * @public
11 | */
12 | export default class DjangoWorkdaysTransform extends Transform {
13 | /**
14 | * Deserialize the string separated by comma into an array of numbers
15 | *
16 | * @method deserialize
17 | * @param {String} serialized The string
18 | * @return {Number[]} The deserialized array
19 | * @public
20 | */
21 | deserialize(serialized) {
22 | return serialized.map(Number);
23 | }
24 |
25 | /**
26 | * Serialize the array of numbers into a string separated by comma
27 | *
28 | * @method serialize
29 | * @param {Number[]} deserialized The number array
30 | * @return {String} The serialized string
31 | * @public
32 | */
33 | serialize(deserialized) {
34 | return deserialized.map(String);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/users/edit/credits/absence-credits/edit/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditCreditsAbsenceCreditsEditRoute extends Route {
4 | model = ({ absence_credit_id: id }) => id;
5 |
6 | setupController(controller, ...args) {
7 | super.setupController(controller, ...args);
8 |
9 | controller.set("user", this.modelFor("users.edit"));
10 | controller.absenceTypes.perform();
11 | controller.credit.perform();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/users/edit/credits/absence-credits/new/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | const EDIT_PATH = "users.edit.credits.absence-credits.edit";
4 |
5 | export default class UsersEditCreditsAbsenceCreditNewRoute extends Route {
6 | controllerName = EDIT_PATH;
7 |
8 | templateName = EDIT_PATH;
9 |
10 | model = () => null;
11 |
12 | setupController(controller, ...args) {
13 | super.setupController(controller, ...args);
14 |
15 | controller.set("user", this.modelFor("users.edit"));
16 | controller.absenceTypes.perform();
17 | controller.credit.perform();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/users/edit/credits/index/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditCreditsIndexRoute extends Route {
4 | model() {
5 | return this.modelFor("users/edit");
6 | }
7 | setupController(controller, model, ...args) {
8 | super.setupController(controller, model, ...args);
9 | controller.years.perform();
10 | controller.absenceCredits.perform();
11 | controller.overtimeCredits.perform();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/users/edit/credits/overtime-credits/edit/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditCreditsOvertimeCreditsRoute extends Route {
4 | model = ({ overtime_credit_id: id }) => id;
5 |
6 | setupController(controller, ...args) {
7 | super.setupController(controller, ...args);
8 |
9 | controller.set("user", this.modelFor("users.edit"));
10 | controller.credit.perform();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/users/edit/credits/overtime-credits/new/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | const EDIT_PATH = "users.edit.credits.overtime-credits.edit";
4 |
5 | export default class UsersEditCreditsOvertimeCreditsNewRoute extends Route {
6 | controllerName = EDIT_PATH;
7 |
8 | templateName = EDIT_PATH;
9 |
10 | model = () => null;
11 |
12 | setupController(controller, ...args) {
13 | super.setupController(controller, ...args);
14 |
15 | controller.set("user", this.modelFor("users.edit"));
16 | controller.credit.perform();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/users/edit/credits/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditCreditsRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/users/edit/credits/template.hbs:
--------------------------------------------------------------------------------
1 | {{outlet}}
--------------------------------------------------------------------------------
/app/users/edit/index/controller.js:
--------------------------------------------------------------------------------
1 | import Controller from "@ember/controller";
2 | import { inject as service } from "@ember/service";
3 | import { task } from "ember-concurrency";
4 | import moment from "moment";
5 |
6 | export default class EditUser extends Controller {
7 | @service store;
8 |
9 | @task
10 | *absences() {
11 | return yield this.store.query("absence", {
12 | user: this.user.id,
13 | ordering: "-date",
14 | // eslint-disable-next-line camelcase
15 | from_date: moment({
16 | day: 1,
17 | month: 0,
18 | year: this.year,
19 | }).format("YYYY-MM-DD"),
20 | include: "absence_type",
21 | });
22 | }
23 |
24 | @task
25 | *employments() {
26 | return yield this.store.query("employment", {
27 | user: this.user.id,
28 | ordering: "-start_date",
29 | include: "location",
30 | });
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/users/edit/index/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditsIndexRoute extends Route {
4 | model() {
5 | return this.modelFor("users/edit");
6 | }
7 |
8 | setupController(controller, model, ...args) {
9 | super.setupController(controller, model, ...args);
10 |
11 | controller.set("user", model);
12 | controller.absences.perform();
13 | controller.employments.perform();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/users/edit/responsibilities/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersEditResponsitibilitesRoute extends Route {
4 | model() {
5 | return this.modelFor("users/edit");
6 | }
7 |
8 | setupController(controller, model, ...args) {
9 | super.setupController(controller, model, ...args);
10 |
11 | controller.set("user", model);
12 | controller.projects.perform();
13 | controller.supervisees.perform();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/users/edit/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 | import { inject as service } from "@ember/service";
3 |
4 | export default class EditUserRoute extends Route {
5 | @service store;
6 | model({ user_id: id }) {
7 | return this.store.findRecord("user", id, { include: "supervisors" });
8 | }
9 |
10 | setupController(controller, model, ...args) {
11 | super.setupController(controller, model, ...args);
12 | controller.data.perform(model.id);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/users/index/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersIndexRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/users/route.js:
--------------------------------------------------------------------------------
1 | import Route from "@ember/routing/route";
2 |
3 | export default class UsersRoute extends Route {}
4 |
--------------------------------------------------------------------------------
/app/users/template.hbs:
--------------------------------------------------------------------------------
1 | {{outlet}}
--------------------------------------------------------------------------------
/app/utils/humanize-duration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-utils
4 | * @public
5 | */
6 |
7 | const { abs, floor } = Math;
8 |
9 | /**
10 | * Converts a moment duration into a string with hours minutes and optionally
11 | * seconds
12 | *
13 | * @function humanizeDuration
14 | * @param {moment.duration} duration The duration to format
15 | * @param {Boolean} seconds Whether to show seconds
16 | * @return {String} The formatted duration
17 | * @public
18 | */
19 | export default function humanizeDuration(duration, seconds = false) {
20 | if (!duration || duration.milliseconds() < 0) {
21 | return seconds ? "0h 0m 0s" : "0h 0m";
22 | }
23 |
24 | const prefix = duration < 0 ? "-" : "";
25 |
26 | // TODO: The locale should be defined by the browser
27 | const h = floor(abs(duration.asHours())).toLocaleString("de-CH");
28 | const m = abs(duration.minutes());
29 |
30 | if (seconds) {
31 | const s = abs(duration.seconds());
32 |
33 | return `${prefix}${h}h ${m}m ${s}s`;
34 | }
35 |
36 | return `${prefix}${h}h ${m}m`;
37 | }
38 |
--------------------------------------------------------------------------------
/app/utils/parse-django-duration.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-utils
4 | * @public
5 | */
6 | import moment from "moment";
7 |
8 | /**
9 | * Converts a django duration string to a moment duration
10 | *
11 | * @function parseDjangoDuration
12 | * @param {String} str The django duration string representation
13 | * @return {moment.duration} The parsed duration
14 | * @public
15 | */
16 | export default function parseDjangoDuration(str) {
17 | if (!str) {
18 | return null;
19 | }
20 |
21 | const re = new RegExp(/^(-?\d+)?\s?(\d{2}):(\d{2}):(\d{2})(\.\d{6})?$/);
22 |
23 | const [, days, hours, minutes, seconds, microseconds] = str
24 | .match(re)
25 | .map((m) => Number(m) || 0);
26 |
27 | return moment.duration({
28 | days,
29 | hours,
30 | minutes,
31 | seconds,
32 | milliseconds: microseconds * 1000,
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/app/utils/serialize-moment.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 |
3 | export const DATE_FORMAT = "YYYY-MM-DD";
4 |
5 | export function serializeMoment(momentObject) {
6 | if (momentObject) {
7 | momentObject = moment(momentObject);
8 | }
9 | return (momentObject && momentObject.format(DATE_FORMAT)) || null;
10 | }
11 | export function deserializeMoment(momentString) {
12 | return (momentString && moment(momentString, DATE_FORMAT)) || null;
13 | }
14 |
--------------------------------------------------------------------------------
/app/utils/url.js:
--------------------------------------------------------------------------------
1 | const notNullOrUndefined = (value) => value !== null && value !== undefined;
2 |
3 | export const cleanParams = (params) =>
4 | Object.keys(params)
5 | .filter((key) => notNullOrUndefined(params[key]))
6 | .reduce((cleaned, key) => ({ ...cleaned, [key]: params[key] }), {});
7 |
8 | export const toQueryString = (params) =>
9 | Object.keys(params)
10 | .map((key) => `${key}=${params[key]}`)
11 | .join("&");
12 |
--------------------------------------------------------------------------------
/app/validations/absence-credit.js:
--------------------------------------------------------------------------------
1 | import {
2 | validatePresence,
3 | validateNumber,
4 | } from "ember-changeset-validations/validators";
5 |
6 | export default {
7 | user: validatePresence(true),
8 | date: validatePresence(true),
9 | absenceType: validatePresence(true),
10 | days: [validatePresence(true), validateNumber({ integer: true })],
11 | };
12 |
--------------------------------------------------------------------------------
/app/validations/absence.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-validations
4 | * @public
5 | */
6 | import { validatePresence } from "ember-changeset-validations/validators";
7 |
8 | /**
9 | * Validations for absences
10 | *
11 | * @class AbsenceValidations
12 | * @public
13 | */
14 | export default {
15 | /**
16 | * Absence type validator, check if an absence type is existent
17 | *
18 | * @property {Function} absenceType
19 | * @public
20 | */
21 | absenceType: validatePresence(true),
22 | };
23 |
--------------------------------------------------------------------------------
/app/validations/activity.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-validations
4 | * @public
5 | */
6 | import validateMoment from "timed/validators/moment";
7 |
8 | /**
9 | * Validations for activities
10 | *
11 | * @class ActivityValidations
12 | * @public
13 | */
14 | export default {
15 | from: validateMoment({ lt: "to" }),
16 | to: validateMoment({ gt: "from" }),
17 | };
18 |
--------------------------------------------------------------------------------
/app/validations/attendance.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-validations
4 | * @public
5 | */
6 | import { validatePresence } from "ember-changeset-validations/validators";
7 | import validateMoment from "timed/validators/moment";
8 |
9 | /**
10 | * Validations for attendances
11 | *
12 | * @class AttendanceValidations
13 | * @public
14 | */
15 | export default {
16 | date: validatePresence(true),
17 | from: [validatePresence(true), validateMoment({ lt: "to" })],
18 | to: [validatePresence(true), validateMoment({ gt: "from" })],
19 | };
20 |
--------------------------------------------------------------------------------
/app/validations/intersection.js:
--------------------------------------------------------------------------------
1 | import validateIntersectionTask from "timed/validators/intersection-task";
2 | import validateNullOrNotBlank from "timed/validators/null-or-not-blank";
3 |
4 | export default {
5 | task: validateIntersectionTask(),
6 | comment: validateNullOrNotBlank(),
7 | };
8 |
--------------------------------------------------------------------------------
/app/validations/multiple-absence.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-validations
4 | * @public
5 | */
6 | import {
7 | validateLength,
8 | validatePresence,
9 | } from "ember-changeset-validations/validators";
10 |
11 | /**
12 | * Validations for multiple absences
13 | *
14 | * @class MultipleAbsenceValidations
15 | * @public
16 | */
17 | export default {
18 | /**
19 | * Absence type validator, check if an absence type is existent
20 | *
21 | * @property {Function} absenceTtype
22 | * @public
23 | */
24 | absenceType: validatePresence(true),
25 |
26 | /**
27 | * Date validation, ensure at least one date is selected
28 | *
29 | * @property {Function} dates
30 | * @public
31 | */
32 | dates: validateLength({
33 | min: 1,
34 | message: "At least one date must be selected",
35 | }),
36 | };
37 |
--------------------------------------------------------------------------------
/app/validations/overtime-credit.js:
--------------------------------------------------------------------------------
1 | import { validatePresence } from "ember-changeset-validations/validators";
2 |
3 | export default {
4 | user: validatePresence(true),
5 | date: validatePresence(true),
6 | duration: validatePresence(true),
7 | };
8 |
--------------------------------------------------------------------------------
/app/validations/project.js:
--------------------------------------------------------------------------------
1 | import { validatePresence } from "ember-changeset-validations/validators";
2 |
3 | export default {
4 | name: validatePresence(true),
5 | billingType: validatePresence(true),
6 | };
7 |
--------------------------------------------------------------------------------
/app/validations/report.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module timed
3 | * @submodule timed-validations
4 | * @public
5 | */
6 | import { validatePresence } from "ember-changeset-validations/validators";
7 |
8 | /**
9 | * Validations for reports
10 | *
11 | * @class ReportValidations
12 | * @public
13 | */
14 | export default {
15 | /**
16 | * Task validator, check if a task is existent
17 | *
18 | * @property {Function} task
19 | * @public
20 | */
21 | task: validatePresence(true),
22 |
23 | /**
24 | * Duration validator, check if a duration is existent
25 | *
26 | * @property {Function} duration
27 | * @public
28 | */
29 | duration: validatePresence(true),
30 | };
31 |
--------------------------------------------------------------------------------
/app/validations/task.js:
--------------------------------------------------------------------------------
1 | import { validatePresence } from "ember-changeset-validations/validators";
2 |
3 | export default {
4 | name: validatePresence(true),
5 | };
6 |
--------------------------------------------------------------------------------
/app/validators/intersection-task.js:
--------------------------------------------------------------------------------
1 | export default function validateIntersectionTask() {
2 | return (key, newValue, oldValue, changes, content) => {
3 | const customerChanged =
4 | Object.keys(changes).includes("customer") &&
5 | (changes.customer?.get("id") || null) !==
6 | (content.customer?.get("id") || null);
7 |
8 | const projectChanged =
9 | Object.keys(changes).includes("project") &&
10 | (changes.project?.get("id") || null) !==
11 | (content.project?.get("id") || null);
12 |
13 | const hasTask = !!(newValue && newValue.id);
14 |
15 | return (
16 | hasTask ||
17 | (!hasTask && !customerChanged && !projectChanged) ||
18 | "Task must not be empty"
19 | );
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/app/validators/null-or-not-blank.js:
--------------------------------------------------------------------------------
1 | import { capitalize } from "@ember/string";
2 |
3 | export default function validateNullOrNotBlank() {
4 | return (key, newValue) => {
5 | return (
6 | !!(newValue === null || (newValue && newValue.length > 0)) ||
7 | `${capitalize(key)} must be null or not blank`
8 | );
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/config/coverage.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | useBabelInstrumenter: true,
3 | babelPlugins: [
4 | "babel-plugin-transform-async-to-generator",
5 | "babel-plugin-transform-decorators-legacy",
6 | "babel-plugin-transform-object-rest-spread",
7 | ],
8 | };
9 |
--------------------------------------------------------------------------------
/config/dependency-lint.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // only lint deps manually
3 | generateTests: false,
4 | };
5 |
--------------------------------------------------------------------------------
/config/deprecation-workflow.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | window.deprecationWorkflow = window.deprecationWorkflow || {};
3 | self.deprecationWorkflow.config = {
4 | workflow: [
5 | { handler: "silence", matchId: "ensure-safe-component.string" }, // optimized-power-select
6 | ],
7 | };
8 |
--------------------------------------------------------------------------------
/config/ember-cli-update.json:
--------------------------------------------------------------------------------
1 | {
2 | "schemaVersion": "1.0.0",
3 | "packages": [
4 | {
5 | "name": "ember-cli",
6 | "version": "4.4.1",
7 | "blueprints": [
8 | {
9 | "name": "app",
10 | "outputRepo": "https://github.com/ember-cli/ember-new-output",
11 | "codemodsSource": "ember-app-codemods-manifest@1",
12 | "isBaseBlueprint": true,
13 | "options": ["--yarn", "--no-welcome"]
14 | }
15 | ]
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/config/icons.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return {
3 | "free-regular-svg-icons": [
4 | "calendar",
5 | "calendar-plus",
6 | "calendar-xmark",
7 | "chart-bar",
8 | "clock",
9 | "eye",
10 | "floppy-disk",
11 | "folder-open",
12 | "hand",
13 | "square",
14 | "square-check",
15 | "circle-xmark",
16 | "trash-can",
17 | "user",
18 | ],
19 | "free-solid-svg-icons": [
20 | "angle-right",
21 | "angle-left",
22 | "arrow-left",
23 | "arrow-right",
24 | "ban",
25 | "bolt",
26 | "briefcase",
27 | "chart-line",
28 | "chevron-left",
29 | "dollar-sign",
30 | "download",
31 | "exclamation-triangle",
32 | "info-circle",
33 | "magnifying-glass",
34 | "mobile-screen-button",
35 | "power-off",
36 | "slash",
37 | "sliders",
38 | "sort",
39 | "sort-down",
40 | "sort-up",
41 | "stop",
42 | "square",
43 | "play",
44 | "plus",
45 | "users",
46 | "question",
47 | "magic",
48 | ],
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/config/optional-features.json:
--------------------------------------------------------------------------------
1 | {
2 | "application-template-wrapper": false,
3 | "default-async-observers": true,
4 | "jquery-integration": false,
5 | "template-only-glimmer-components": true
6 | }
7 |
--------------------------------------------------------------------------------
/config/targets.js:
--------------------------------------------------------------------------------
1 | "use-strict";
2 |
3 | const browsers = [
4 | "last 1 Chrome versions",
5 | "last 1 Firefox versions",
6 | "last 1 Safari versions",
7 | ];
8 |
9 | module.exports = {
10 | browsers,
11 | };
12 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | services:
4 | db:
5 | image: postgres:9.4
6 | ports:
7 | - 5432:5432
8 | volumes:
9 | - dbdata:/var/lib/postgresql/data
10 | environment:
11 | - POSTGRES_USER=timed
12 | - POSTGRES_PASSWORD=timed
13 |
14 | frontend:
15 | build:
16 | context: .
17 | ports:
18 | - 4200:80
19 |
20 | backend:
21 | image: ghcr.io/adfinis/timed-backend:latest
22 | ports:
23 | - 8000:80
24 | depends_on:
25 | - db
26 | - mailhog
27 | environment:
28 | - DJANGO_DATABASE_HOST=db
29 | - DJANGO_DATABASE_PORT=5432
30 | - ENV=docker
31 | - STATIC_ROOT=/var/www/static
32 | - EMAIL_URL=smtp://mailhog:1025
33 | command: /bin/sh -c "wait-for-it.sh -t 60 db:5432 -- ./manage.py migrate && ./manage.py loaddata timed/fixtures/test_data.json && uwsgi"
34 |
35 | mailhog:
36 | image: mailhog/mailhog
37 | ports:
38 | - 8025:8025
39 | environment:
40 | - MH_UI_WEB_PATH=mailhog
41 |
42 | volumes:
43 | dbdata:
44 |
--------------------------------------------------------------------------------
/docker-entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | set -eu
4 |
5 | urlencode() {
6 | # urlencode
7 | # blatantly pinched from https://gist.github.com/cdown/1163649
8 |
9 | local length="${#1}"
10 | for i in $(seq 0 $((length-1))); do
11 | local c="${1:i:1}"
12 | case $c in
13 | [a-zA-Z0-9.~_-]) printf "$c" ;;
14 | *) printf '%%%02X' "'$c" ;;
15 | esac
16 | done
17 | }
18 |
19 | sed -i \
20 | -e "s/sso-client-id/$(urlencode ${TIMED_SSO_CLIENT_ID})/g" \
21 | -e "s/sso-client-host/$(urlencode ${TIMED_SSO_CLIENT_HOST})/g" \
22 | /var/www/html/index.html
23 |
24 | exec "$@"
25 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // eslint-disable-next-line n/no-missing-require
4 | const Funnel = require("broccoli-funnel");
5 | const EmberApp = require("ember-cli/lib/broccoli/ember-app");
6 |
7 | module.exports = function (defaults) {
8 | const app = new EmberApp(defaults, {
9 | sassOptions: {
10 | onlyIncluded: true,
11 | },
12 | "ember-fetch": {
13 | preferNative: true,
14 | },
15 | "ember-simple-auth": {
16 | useSessionSetupMethod: true,
17 | },
18 | "ember-validated-form": {
19 | theme: "bootstrap",
20 | },
21 | });
22 |
23 | app.import("node_modules/@fontsource/source-sans-pro/index.css");
24 |
25 | app.import("node_modules/simplebar/dist/simplebar.css");
26 |
27 | app.import("node_modules/downloadjs/download.min.js", {
28 | using: [{ transformation: "amd", as: "downloadjs" }],
29 | });
30 |
31 | const fonts = new Funnel("node_modules/@fontsource/source-sans-pro/files", {
32 | include: ["*.woff", "*.woff2"],
33 | destDir: "/assets/files/",
34 | });
35 |
36 | return app.toTree([fonts]);
37 | };
38 |
--------------------------------------------------------------------------------
/mirage/factories/absence-balance.js:
--------------------------------------------------------------------------------
1 | import { Factory, trait } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | import { randomDuration } from "../helpers/duration";
6 |
7 | export default Factory.extend({
8 | date: () => moment(),
9 | balance: () => randomDuration(),
10 |
11 | days: trait({
12 | credit: () => faker.random.number({ min: 10, max: 20 }),
13 | usedDays: () => faker.random.number({ min: 5, max: 25 }),
14 | }),
15 |
16 | duration: trait({
17 | usedDuration: () => randomDuration(),
18 | }),
19 | });
20 |
--------------------------------------------------------------------------------
/mirage/factories/absence-credit.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | export default Factory.extend({
6 | date: () => moment().format("YYYY-MM-DD"),
7 | days: () => faker.random.number({ min: 1, max: 25 }),
8 | comment: () => faker.lorem.sentence(),
9 |
10 | afterCreate(absenceCredit, server) {
11 | absenceCredit.update({ absenceTypeId: server.db.absenceTypes[0].id });
12 | },
13 | });
14 |
--------------------------------------------------------------------------------
/mirage/factories/absence-type.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | name: () => faker.lorem.word(),
6 | fillWorktime: false,
7 | });
8 |
--------------------------------------------------------------------------------
/mirage/factories/absence.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | export default Factory.extend({
6 | comment: () => faker.lorem.sentence(),
7 | date: () => moment().format("YYYY-MM-DD"),
8 | duration: () => "08:30:00",
9 |
10 | afterCreate(absence, server) {
11 | absence.update({
12 | absenceTypeId: server.schema.absenceTypes.all().models[0].id,
13 | });
14 | },
15 | });
16 |
--------------------------------------------------------------------------------
/mirage/factories/attendance.js:
--------------------------------------------------------------------------------
1 | import { Factory, trait } from "ember-cli-mirage";
2 | import moment from "moment";
3 |
4 | export default Factory.extend({
5 | date: moment().format("YYYY-MM-DD"),
6 |
7 | morning: trait({
8 | fromTime: "08:00:00",
9 | toTime: "11:30:00",
10 | }),
11 |
12 | afternoon: trait({
13 | fromTime: "12:00:00",
14 | toTime: "17:00:00",
15 | }),
16 | });
17 |
--------------------------------------------------------------------------------
/mirage/factories/billing-type.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | name: () => faker.lorem.word(),
6 | });
7 |
--------------------------------------------------------------------------------
/mirage/factories/cost-center.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | name: () => faker.finance.accountName(),
6 | reference: () => faker.finance.account(),
7 | });
8 |
--------------------------------------------------------------------------------
/mirage/factories/customer-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | duration: () => randomDuration(15, false, 20),
7 |
8 | afterCreate(customerStatistic, server) {
9 | customerStatistic.update({ customerId: server.create("customer").id });
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/mirage/factories/customer.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | name: () => faker.company.companyName(),
6 | });
7 |
--------------------------------------------------------------------------------
/mirage/factories/employment.js:
--------------------------------------------------------------------------------
1 | import { Factory, trait } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 | import DjangoDurationTransform from "timed/transforms/django-duration";
5 |
6 | export default Factory.extend({
7 | percentage: faker.random.arrayElement([50, 60, 80, 100]),
8 | // location: association(),
9 | // user: association(),
10 |
11 | isExternal: false,
12 |
13 | worktimePerDay() {
14 | const worktime = moment.duration(
15 | (moment.duration({ h: 8, m: 30 }) / 100) * this.percentage
16 | );
17 |
18 | return DjangoDurationTransform.create().serialize(worktime);
19 | },
20 |
21 | start: () => faker.date.past(4),
22 | end: () => faker.date.past(1),
23 |
24 | active: trait({
25 | start: () => faker.date.recent(),
26 | end: null,
27 | }),
28 |
29 | afterCreate(employment, server) {
30 | employment.update({ locationId: server.create("location").id });
31 | },
32 | });
33 |
--------------------------------------------------------------------------------
/mirage/factories/location.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | name: () => faker.address.city(),
6 | workdays: () => ["1", "2", "3", "4", "5"],
7 | });
8 |
--------------------------------------------------------------------------------
/mirage/factories/month-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | year: (i) => 2010 + Math.ceil((i + 1) / 12),
7 | month: (i) => (i % 12) + 1,
8 | duration: () => randomDuration(15, false, 20),
9 | });
10 |
--------------------------------------------------------------------------------
/mirage/factories/overtime-credit.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | import { randomDuration } from "../helpers/duration";
6 |
7 | export default Factory.extend({
8 | date: () => moment().format("YYYY-MM-DD"),
9 | duration: () => randomDuration(),
10 | comment: () => faker.lorem.sentence(),
11 | });
12 |
--------------------------------------------------------------------------------
/mirage/factories/project-assignee.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | export default Factory.extend({
4 | isReviewer: true,
5 |
6 | afterCreate(projectAssignee, server) {
7 | const project = server.create("project");
8 | const user = server.create("user");
9 | projectAssignee.update({ project });
10 | projectAssignee.update({ user });
11 | },
12 | });
13 |
--------------------------------------------------------------------------------
/mirage/factories/project-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | duration: () => randomDuration(15, false, 20),
7 |
8 | afterCreate(projectStatistic, server) {
9 | projectStatistic.update({ projectId: server.create("project").id });
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/mirage/factories/project.js:
--------------------------------------------------------------------------------
1 | import { Factory, association, trait } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | import { randomDuration } from "../helpers/duration";
5 |
6 | export default Factory.extend({
7 | name: () => faker.commerce.productName(),
8 | estimatedTime: () => randomDuration(),
9 |
10 | afterCreate(project, server) {
11 | project.update({ customerId: server.create("customer").id });
12 | },
13 |
14 | withBillingType: trait({
15 | billingType: association(),
16 | }),
17 | });
18 |
--------------------------------------------------------------------------------
/mirage/factories/public-holiday.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | export default Factory.extend({
6 | name: () => faker.lorem.word(),
7 | // location: association(),
8 |
9 | date() {
10 | const random = faker.date.between(
11 | moment.startOf("year").format("YYYY-MM-DD"),
12 | moment.endOf("year").format("YYYY-MM-DD")
13 | );
14 |
15 | return moment(random).startOf("day");
16 | },
17 |
18 | afterCreate(publicHoliday, server) {
19 | publicHoliday.update({ locationId: server.create("location").id });
20 | },
21 | });
22 |
--------------------------------------------------------------------------------
/mirage/factories/report-intersection.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 |
4 | export default Factory.extend({
5 | comment: () => faker.lorem.sentence(),
6 | notBillable: () => faker.random.boolean(),
7 | review: () => faker.random.boolean(),
8 | verified: () => faker.random.boolean(),
9 |
10 | afterCreate(intersection, server) {
11 | const task = server.create("task");
12 |
13 | intersection.update({
14 | customerId: task.project.customer.id,
15 | projectId: task.project.id,
16 | taskId: task.id,
17 | });
18 | },
19 | });
20 |
--------------------------------------------------------------------------------
/mirage/factories/report.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import faker from "faker";
3 | import moment from "moment";
4 |
5 | import { randomDuration } from "../helpers/duration";
6 |
7 | export default Factory.extend({
8 | comment: () => faker.lorem.sentence(),
9 | date: () => moment().format("YYYY-MM-DD"),
10 | duration: () => randomDuration(),
11 | review: () => faker.random.boolean(),
12 | notBillable: () => faker.random.boolean(),
13 | verifiedBy: null,
14 |
15 | afterCreate(report, server) {
16 | report.update({ taskId: server.create("task").id });
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/mirage/factories/task-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | duration: () => randomDuration(15, false, 20),
7 |
8 | afterCreate(taskStatistic, server) {
9 | taskStatistic.update({ taskId: server.create("task").id });
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/mirage/factories/task.js:
--------------------------------------------------------------------------------
1 | import { capitalize } from "@ember/string";
2 | import { Factory } from "ember-cli-mirage";
3 | import faker from "faker";
4 |
5 | import { randomDuration } from "../helpers/duration";
6 |
7 | export default Factory.extend({
8 | name: () => capitalize(faker.hacker.ingverb()),
9 | estimatedTime: () => randomDuration(),
10 |
11 | afterCreate(task, server) {
12 | task.update({ projectId: server.create("project").id });
13 | },
14 | });
15 |
--------------------------------------------------------------------------------
/mirage/factories/user-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | duration: () => randomDuration(15, false, 20),
7 |
8 | afterCreate(userStatistic, server) {
9 | userStatistic.update({ userId: server.create("user").id });
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/mirage/factories/worktime-balance.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 | import moment from "moment";
3 |
4 | import { randomDuration } from "../helpers/duration";
5 |
6 | export default Factory.extend({
7 | date: () => moment(),
8 | balance: () => randomDuration(),
9 | });
10 |
--------------------------------------------------------------------------------
/mirage/factories/year-statistic.js:
--------------------------------------------------------------------------------
1 | import { Factory } from "ember-cli-mirage";
2 |
3 | import { randomDuration } from "../helpers/duration";
4 |
5 | export default Factory.extend({
6 | year: (i) => 2010 + i,
7 | duration: () => randomDuration(15, false, 20),
8 | });
9 |
--------------------------------------------------------------------------------
/mirage/fixtures/absence-types.js:
--------------------------------------------------------------------------------
1 | export default [
2 | { id: 1, name: "Ferien" },
3 | { id: 2, name: "EO" },
4 | { id: 3, name: "Krankheit" },
5 | ];
6 |
--------------------------------------------------------------------------------
/mirage/helpers/duration.js:
--------------------------------------------------------------------------------
1 | import faker from "faker";
2 | import moment from "moment";
3 | import DjangoDurationTransform from "timed/transforms/django-duration";
4 |
5 | export function randomDuration(precision = 15, seconds = false, maxHours = 2) {
6 | const h = faker.random.number({ max: maxHours });
7 | const m = Math.abs(
8 | Math.ceil(faker.random.number({ min: 0, max: 60 }) / precision) * precision
9 | );
10 | const s = Math.abs(seconds ? faker.random.number({ max: 59, min: 0 }) : 0);
11 |
12 | return DjangoDurationTransform.create().serialize(
13 | moment.duration({ h, m, s })
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/mirage/serializers/application.js:
--------------------------------------------------------------------------------
1 | import { JSONAPISerializer } from "ember-cli-mirage";
2 |
3 | export default JSONAPISerializer.extend({
4 | alwaysIncludeLinkageData: true,
5 | });
6 |
--------------------------------------------------------------------------------
/public/assets/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/public/assets/favicon-16x16.png
--------------------------------------------------------------------------------
/public/assets/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/public/assets/favicon-32x32.png
--------------------------------------------------------------------------------
/public/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/public/assets/favicon.ico
--------------------------------------------------------------------------------
/public/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/public/assets/logo.png
--------------------------------------------------------------------------------
/public/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/public/assets/logo_text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/public/assets/logo_text.png
--------------------------------------------------------------------------------
/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow: /
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["config:base", ":rebaseStalePrs"],
4 | "automerge": true,
5 | "automergeSchedule": ["after 12am on monday"],
6 | "automergeType": "branch",
7 | "major": {
8 | "automerge": false
9 | },
10 | "schedule": ["before 2am on monday"],
11 | "stabilityDays": 3
12 | }
13 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | "use-strict";
2 |
3 | module.exports = {
4 | test_page: "tests/index.html?hidepassed",
5 | disable_watching: true,
6 | parallel: -1,
7 | launch_in_dev: [],
8 | launch_in_ci: ["chrome"],
9 | browser_start_timeout: 120,
10 | browser_args: {
11 | Chrome: {
12 | ci: [
13 | // --no-sandbox is needed when running Chrome inside a container
14 | process.env.CI ? "--no-sandbox" : null,
15 | "--headless",
16 | "--disable-gpu",
17 | "--disable-dev-shm-usage",
18 | "--disable-software-rasterizer",
19 | "--mute-audio",
20 | "--remote-debugging-port=9222",
21 | "--window-size=1440,900",
22 | ].filter(Boolean),
23 | },
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | module.exports = {
4 | env: {
5 | embertest: true,
6 | node: true,
7 | },
8 | globals: {
9 | server: true,
10 | taskSelect: true,
11 | userSelect: true,
12 | setBreakpoint: true,
13 | selectChoose: true,
14 | selectSearch: true,
15 | removeMultipleOption: true,
16 | clearSelected: true,
17 | },
18 | rules: {
19 | "no-magic-numbers": "off",
20 | "require-jsdoc": "off",
21 | },
22 | };
23 |
--------------------------------------------------------------------------------
/tests/acceptance/notfound-test.js:
--------------------------------------------------------------------------------
1 | import { visit } from "@ember/test-helpers";
2 | import { setupMirage } from "ember-cli-mirage/test-support";
3 | import { setupApplicationTest } from "ember-qunit";
4 | import { authenticateSession } from "ember-simple-auth/test-support";
5 | import { module, test } from "qunit";
6 |
7 | module("Acceptance | notfound", function (hooks) {
8 | setupApplicationTest(hooks);
9 | setupMirage(hooks);
10 |
11 | test("displays a 404 page for undefined routes if logged in", async function (assert) {
12 | const user = this.server.create("user");
13 |
14 | // eslint-disable-next-line camelcase
15 | await authenticateSession({ user_id: user.id });
16 |
17 | await visit("/thiswillneverbeavalidrouteurl");
18 |
19 | assert.dom("[data-test-notfound]").exists({ count: 1 });
20 | });
21 | });
22 |
--------------------------------------------------------------------------------
/tests/helpers/session-mock.js:
--------------------------------------------------------------------------------
1 | import Service from "@ember/service";
2 |
3 | class SessionMock extends Service {
4 | isAuthenticated = true;
5 | headers = {
6 | authorization: "Bearer TEST1234",
7 | };
8 | }
9 |
10 | export default function setupSession(hooks) {
11 | hooks.beforeEach(async function () {
12 | this.owner.register("service:session", SessionMock);
13 | });
14 | }
15 |
--------------------------------------------------------------------------------
/tests/helpers/task-select.js:
--------------------------------------------------------------------------------
1 | import { selectChoose } from "ember-power-select/test-support";
2 |
3 | export default async function (
4 | selector = "",
5 | options = { fromHistory: false }
6 | ) {
7 | if (options.fromHistory) {
8 | await selectChoose(
9 | `${selector} .customer-select`,
10 | ".ember-power-select-option",
11 | 0
12 | );
13 |
14 | return;
15 | }
16 |
17 | await selectChoose(
18 | `${selector} .customer-select`,
19 | ".ember-power-select-option",
20 | 1
21 | );
22 | await selectChoose(
23 | `${selector} .project-select`,
24 | ".ember-power-select-option",
25 | 0
26 | );
27 | await selectChoose(
28 | `${selector} .task-select`,
29 | ".ember-power-select-option",
30 | 0
31 | );
32 | }
33 |
--------------------------------------------------------------------------------
/tests/helpers/tracking-mock.js:
--------------------------------------------------------------------------------
1 | import Service from "@ember/service";
2 |
3 | const LOCAL_OVERRIDES = {
4 | activity: {
5 | comment: "",
6 | },
7 | customers: [],
8 | recentTasks: [],
9 | };
10 |
11 | class TrackingServiceStub extends Service {
12 | get activity() {
13 | return LOCAL_OVERRIDES.activity;
14 | }
15 |
16 | get customers() {
17 | return LOCAL_OVERRIDES.customers;
18 | }
19 |
20 | get recentTasks() {
21 | return LOCAL_OVERRIDES.recentTasks;
22 | }
23 | }
24 |
25 | export function setup(context, overrides) {
26 | context.owner.register("service:tracking", TrackingServiceStub);
27 |
28 | const service = context.owner.lookup("service:tracking");
29 |
30 | Object.keys(overrides).forEach((key) => {
31 | if (LOCAL_OVERRIDES[key]) {
32 | LOCAL_OVERRIDES[key] = overrides[key];
33 | } else {
34 | service[key] = overrides[key];
35 | }
36 | });
37 | }
38 |
39 | export function teardown(context) {
40 | context.owner.unregister("service:tracking");
41 | }
42 |
--------------------------------------------------------------------------------
/tests/helpers/user-select.js:
--------------------------------------------------------------------------------
1 | import { selectChoose } from "ember-power-select/test-support";
2 |
3 | export default async function (selector = "") {
4 | await selectChoose(
5 | `${selector} .user-select`,
6 | ".ember-power-select-option",
7 | 0
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/tests/integration/components/changed-warning/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | changed warning", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{changed-warning}}`);
11 |
12 | assert.dom(".fa-triangle-exclamation").exists();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/customer-visible-icon/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | customer visible icon", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{customer-visible-icon}}`);
11 |
12 | assert.dom(".fa-eye").exists();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/filter-sidebar/label/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | filter sidebar/label", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`
11 | {{#filter-sidebar/label}}
12 | Some label
13 | {{/filter-sidebar/label}}
14 | `);
15 |
16 | assert.dom("label").exists();
17 | assert.dom("label").hasText("Some label");
18 | });
19 | });
20 |
--------------------------------------------------------------------------------
/tests/integration/components/in-viewport/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | in viewport", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`
11 |
16 | `);
17 |
18 | assert.dom(".child").includesText("test");
19 | });
20 | });
21 |
--------------------------------------------------------------------------------
/tests/integration/components/loading-icon/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | loading icon", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs` `);
11 |
12 | assert.dom(".loading-dot").exists({ count: 9 });
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/no-mobile-message/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupMirage } from "ember-cli-mirage/test-support";
4 | import { setupRenderingTest } from "ember-qunit";
5 | import { module, test } from "qunit";
6 |
7 | module("Integration | Component | no mobile message", function (hooks) {
8 | setupRenderingTest(hooks);
9 | setupMirage(hooks);
10 |
11 | test("renders", async function (assert) {
12 | await render(hbs` `);
13 | assert.ok(this.element);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/integration/components/no-permission/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | no permission", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs` `);
11 |
12 | assert.dom(".empty").exists();
13 | assert.dom(".empty").includesText("Halt");
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/integration/components/not-identical-warning/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | not identical warning", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs` `);
11 |
12 | assert.dom(".fa-circle-info").exists();
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/report-review-warning/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | report review warning", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{report-review-warning}}`);
11 | assert.ok(this.element);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tests/integration/components/sort-header/component-test.js:
--------------------------------------------------------------------------------
1 | import { click, render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | sort header", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs` `);
11 | assert.dom(".fa-sort").exists({ count: 1 });
12 | });
13 |
14 | test("renders active state", async function (assert) {
15 | this.set("current", "-test");
16 | this.set("update", (sort) => {
17 | this.set("current", sort);
18 | });
19 |
20 | await render(
21 | hbs` `
22 | );
23 | assert.dom(".fa-sort-down").exists({ count: 1 });
24 |
25 | await click(".sort-header");
26 | assert.dom(".fa-sort-up").exists({ count: 1 });
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-checkmark/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | sy checkmark", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("works unchecked", async function (assert) {
10 | await render(hbs` `);
11 | assert.dom(".fa-square").exists({ count: 1 });
12 | });
13 |
14 | test("works checked", async function (assert) {
15 | await render(hbs` `);
16 | assert.dom(".fa-square-check").exists({ count: 1 });
17 | });
18 |
19 | test("works highlight", async function (assert) {
20 | await render(hbs` `);
21 | assert.dom(".fa-square-check").exists({ count: 1 });
22 | assert.dom(".highlight").exists({ count: 1 });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-durationpicker-day/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | sy durationpicker day", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{sy-durationpicker-day}}`);
11 | assert.ok(this.element);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-modal-target/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | sy modal target", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs` `);
11 | assert.dom("#sy-modals").exists({ count: 1 });
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-modal/body/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | sy modal/body", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`Test `);
11 |
12 | assert.dom(this.element).hasText("Test");
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-modal/footer/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | SyModal::Footer", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`Test `);
11 |
12 | assert.dom(this.element).hasText("Test");
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-modal/header/component-test.js:
--------------------------------------------------------------------------------
1 | import { click, render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | SyModal::Header", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | this.set("visible", true);
11 |
12 | await render(hbs`
13 |
16 | Test
17 |
18 | `);
19 |
20 | assert.dom(this.element).hasText("Test ×");
21 | });
22 |
23 | test("closes on click of the close icon", async function (assert) {
24 | this.set("visible", true);
25 |
26 | await render(hbs`
27 |
30 | Test
31 |
32 | `);
33 |
34 | assert.ok(this.visible);
35 |
36 | await click("button");
37 |
38 | assert.notOk(this.visible);
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/tests/integration/components/sy-topnav/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupMirage } from "ember-cli-mirage/test-support";
4 | import { setupRenderingTest } from "ember-qunit";
5 | import { module, test } from "qunit";
6 |
7 | module("Integration | Component | sy topnav", function (hooks) {
8 | setupRenderingTest(hooks);
9 | setupMirage(hooks);
10 |
11 | test("renders", async function (assert) {
12 | await render(hbs` `);
13 | assert.ok(this.element);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/integration/components/timed-clock/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | timed clock", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{timed-clock}}`);
11 | assert.ok(this.element);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tests/integration/components/tracking-bar/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 | import { setup as setupTrackingService } from "timed/tests/helpers/tracking-mock";
6 |
7 | module("Integration | Component | tracking bar", function (hooks) {
8 | setupRenderingTest(hooks);
9 |
10 | hooks.beforeEach(function () {
11 | setupTrackingService(this, {
12 | activity: { comment: "asdf" },
13 | fetchRecentTasks: { last: Promise.resolve() },
14 | fetchCustomers: {
15 | perform: () => {},
16 | last: Promise.resolve(),
17 | },
18 | });
19 | });
20 |
21 | test("renders", async function (assert) {
22 | await render(hbs` `);
23 |
24 | assert.dom("input[type=text]").hasValue("asdf");
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/integration/components/user-selection/component-test.js:
--------------------------------------------------------------------------------
1 | import { find, render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupMirage } from "ember-cli-mirage/test-support";
4 | import { setupRenderingTest } from "ember-qunit";
5 | import { module, test } from "qunit";
6 |
7 | module("Integration | Component | user selection", function (hooks) {
8 | setupRenderingTest(hooks);
9 | setupMirage(hooks);
10 |
11 | test("renders", async function (assert) {
12 | assert.expect(1);
13 | const user = this.server.create("user");
14 | this.set("user", user);
15 |
16 | await render(hbs`
17 |
18 | {{u.user}}
19 |
20 | `);
21 |
22 | assert.strictEqual(
23 | find(".user-select .ember-power-select-selected-item").textContent.trim(),
24 | user.longName
25 | );
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/tests/integration/components/weekly-overview-benchmark/component-test.js:
--------------------------------------------------------------------------------
1 | import { find, render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | weekly overview benchmark", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`{{weekly-overview-benchmark hours=20}}`);
11 |
12 | assert.ok(this.element);
13 | });
14 |
15 | test("computes the position correctly", async function (assert) {
16 | await render(hbs`{{weekly-overview-benchmark hours=10 max=10}}`);
17 |
18 | assert.strictEqual(find("hr").getAttribute("style"), "bottom: calc(100%);");
19 | });
20 |
21 | test("shows labels only when permitted", async function (assert) {
22 | await render(hbs`{{weekly-overview-benchmark showLabel=true hours=8.5}}`);
23 |
24 | assert.strictEqual(find("span").textContent, "8.5h");
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/integration/components/welcome-modal/component-test.js:
--------------------------------------------------------------------------------
1 | import { render } from "@ember/test-helpers";
2 | import { hbs } from "ember-cli-htmlbars";
3 | import { setupRenderingTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Integration | Component | welcome modal", function (hooks) {
7 | setupRenderingTest(hooks);
8 |
9 | test("renders", async function (assert) {
10 | await render(hbs`
11 |
12 |
13 | `);
14 | assert.ok(this.element);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import { setApplication } from "@ember/test-helpers";
2 | import { start } from "ember-qunit";
3 | import setupSinon from "ember-sinon-qunit";
4 | import * as QUnit from "qunit";
5 | import { setup } from "qunit-dom";
6 |
7 | import Application from "../app";
8 | import config from "../config/environment";
9 |
10 | setApplication(Application.create(config.APP));
11 |
12 | setup(QUnit.assert);
13 |
14 | setupSinon();
15 |
16 | start();
17 |
--------------------------------------------------------------------------------
/tests/unit/analysis/edit/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | analysis/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:analysis/index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/analysis/edit/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | analysis/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:analysis/edit");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/analysis/index/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | analysis/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:analysis/index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/analysis/index/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | analysis/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:analysis/index");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/analysis/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | analysis", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:analysis");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/controllers/qpcontroller/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | controllers/qpcontroller", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("get all query params", function (assert) {
8 | const controller = this.owner.lookup("controller:qpcontroller");
9 | controller.queryParams = ["qp1", "qp2"];
10 | controller.qp1 = "baz";
11 | controller.qp2 = "foo";
12 |
13 | assert.strictEqual(controller.allQueryParams.qp1, "baz");
14 | assert.strictEqual(controller.allQueryParams.qp2, "foo");
15 | });
16 |
17 | test("reset query params", function (assert) {
18 | const controller = this.owner.lookup("controller:qpcontroller");
19 | controller.queryParams = ["qp1", "qp2"];
20 | controller.qp1 = "baz";
21 | controller.qp2 = "foo";
22 |
23 | controller.resetQueryParams();
24 |
25 | assert.strictEqual(controller.allQueryParams.qp1, undefined);
26 | assert.strictEqual(controller.allQueryParams.qp2, undefined);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/tests/unit/helpers/format-duration-test.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import { module, test } from "qunit";
3 | import { formatDurationFn } from "timed/helpers/format-duration";
4 |
5 | module("Unit | Helper | format duration", function () {
6 | test("works", function (assert) {
7 | const duration = moment.duration({
8 | hours: 3,
9 | minutes: 56,
10 | seconds: 59,
11 | });
12 |
13 | const result = formatDurationFn([duration]);
14 |
15 | assert.strictEqual(result, "03:56:59");
16 | });
17 |
18 | test("works without seconds", function (assert) {
19 | const duration = moment.duration({
20 | hours: 3,
21 | minutes: 56,
22 | seconds: 59,
23 | });
24 |
25 | const result = formatDurationFn([duration, false]);
26 |
27 | assert.strictEqual(result, "03:56");
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/unit/helpers/humanize-duration-test.js:
--------------------------------------------------------------------------------
1 | import moment from "moment";
2 | import { module, test } from "qunit";
3 | import { humanizeDurationFn } from "timed/helpers/humanize-duration";
4 |
5 | module("Unit | Helper | humanize duration", function () {
6 | test("works", function (assert) {
7 | const duration = moment.duration({
8 | hours: 3,
9 | minutes: 56,
10 | seconds: 59,
11 | });
12 |
13 | const result = humanizeDurationFn([duration]);
14 |
15 | assert.strictEqual(result, "3h 56m");
16 | });
17 |
18 | test("works with seconds", function (assert) {
19 | const duration = moment.duration({
20 | hours: 3,
21 | minutes: 56,
22 | seconds: 59,
23 | });
24 |
25 | const result = humanizeDurationFn([duration, true]);
26 |
27 | assert.strictEqual(result, "3h 56m 59s");
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/tests/unit/helpers/parse-django-duration-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from "qunit";
2 | import { parseDjangoDurationFn } from "timed/helpers/parse-django-duration";
3 |
4 | module("Unit | Helper | parse django duration", function () {
5 | test("works", function (assert) {
6 | const result = parseDjangoDurationFn(["11:30:00"]);
7 |
8 | assert.strictEqual(result.asHours(), 11.5);
9 | });
10 |
11 | test("works with a negative duration", function (assert) {
12 | const result = parseDjangoDurationFn(["-1 11:30:00"]);
13 |
14 | assert.strictEqual(result.asHours(), -12.5);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/index/activities/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | index/activities", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:index/activities");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/activities/edit/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | index/activities/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:index/activities/edit");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/activities/edit/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | index/activities/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:index/activities/edit");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/activities/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | index/activities", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:index/activities");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/attendances/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | index/attendances", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:index/attendances");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/attendances/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | index/attendances", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:index/attendances");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/reports/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | index/reports", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:index/reports");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/reports/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | index/reports", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:index/reports");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/index/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:index");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/login/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | login", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:login");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/models/absence-balance-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | absence balance", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("absence-balance");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/activity-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | activity", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").createRecord("activity");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/attendance-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import moment from "moment";
3 | import { module, test } from "qunit";
4 |
5 | module("Unit | Model | attendance", function (hooks) {
6 | setupTest(hooks);
7 |
8 | test("exists", function (assert) {
9 | const model = this.owner.lookup("service:store").modelFor("attendance");
10 |
11 | assert.ok(model);
12 | });
13 |
14 | test("calculates the duration", function (assert) {
15 | const model = this.owner
16 | .lookup("service:store")
17 | .createRecord("attendance", {
18 | from: moment({ h: 8, m: 0, s: 0 }),
19 | to: moment({ h: 17, m: 0, s: 0 }),
20 | });
21 |
22 | assert.strictEqual(model.get("duration").asHours(), 9);
23 | });
24 |
25 | test("calculates the duration when the end time is 00:00", function (assert) {
26 | const model = this.owner
27 | .lookup("service:store")
28 | .createRecord("attendance", {
29 | from: moment({ h: 0, m: 0, s: 0 }),
30 | to: moment({ h: 0, m: 0, s: 0 }),
31 | });
32 |
33 | assert.strictEqual(model.get("duration").asHours(), 24);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/tests/unit/models/billing-type-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | billing type", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("billing-type");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/cost-center-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | cost center", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("cost-center");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/customer-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | customer statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("customer-statistic");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/customer-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | customer", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("customer");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/employment-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | employment", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("employment");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/location-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | location", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("location");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/month-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | month statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("month-statistic");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/overtime-credit-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | overtime credit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("overtime-credit");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/project-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | project statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("project-statistic");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/project-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | project", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("project");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/public-holiday-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | public holiday", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("public-holiday");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/report-intersection-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | report intersection", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("report-intersection");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/report-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | report", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("report");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/task-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | task statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("task-statistic");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/task-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | task", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("task");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/user-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | user statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("user-statistic");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/models/worktime-balance-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | worktime balance", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner
9 | .lookup("service:store")
10 | .modelFor("worktime-balance");
11 |
12 | assert.ok(model);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/tests/unit/models/year-statistic-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Model | year statistic", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const model = this.owner.lookup("service:store").modelFor("year-statistic");
9 |
10 | assert.ok(model);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/no-access/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | no-access", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("it exists", function (assert) {
8 | const route = this.owner.lookup("route:no-access");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/notfound/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | notfound", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:notfound");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/projects/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | projects", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:projects");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/projects/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | projects", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:projects");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/protected/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | protected", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:protected");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/protected/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | protected", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:protected");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/serializers/attendance-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Serializer | attendance", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("serializes records", function (assert) {
8 | const record = this.owner
9 | .lookup("service:store")
10 | .createRecord("attendance");
11 |
12 | const serializedRecord = record.serialize();
13 |
14 | assert.ok(serializedRecord);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/serializers/employment-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Serializer | employment", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("serializes records", function (assert) {
8 | const record = this.owner
9 | .lookup("service:store")
10 | .createRecord("employment");
11 |
12 | const serializedRecord = record.serialize();
13 |
14 | assert.ok(serializedRecord);
15 | });
16 | });
17 |
--------------------------------------------------------------------------------
/tests/unit/services/fetch-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 | import setupSession from "timed/tests/helpers/session-mock";
4 |
5 | module("Unit | Service | fetch", function (hooks) {
6 | setupTest(hooks);
7 | setupSession(hooks);
8 |
9 | test("exists", function (assert) {
10 | const service = this.owner.lookup("service:fetch");
11 | assert.ok(service);
12 | });
13 |
14 | test("adds the auth token to the headers", function (assert) {
15 | const service = this.owner.lookup("service:fetch");
16 | const session = this.owner.lookup("service:session");
17 |
18 | assert.strictEqual(
19 | service.get("headers.authorization"),
20 | session.headers.authorization
21 | );
22 | });
23 |
24 | test("does not add the auth token to the headers if no token is given", function (assert) {
25 | const service = this.owner.lookup("service:fetch");
26 | const session = this.owner.lookup("service:session");
27 |
28 | delete session.headers.authorization;
29 |
30 | assert.notOk(service.get("headers.authorization"));
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/tests/unit/services/metadata-fetcher-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Service | metadata fetcher", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const service = this.owner.lookup("service:metadata-fetcher");
9 | assert.ok(service);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/services/rejected-reports-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Service | rejectedReports", function (hooks) {
5 | setupTest(hooks);
6 |
7 | // TODO: Replace this with your real tests.
8 | test("it exists", function (assert) {
9 | const service = this.owner.lookup("service:rejected-reports");
10 | assert.ok(service);
11 | });
12 | });
13 |
--------------------------------------------------------------------------------
/tests/unit/services/tracking-test.js:
--------------------------------------------------------------------------------
1 | import { settled } from "@ember/test-helpers";
2 | import { setupMirage } from "ember-cli-mirage/test-support";
3 | import { setupTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Unit | Service | tracking", function (hooks) {
7 | setupTest(hooks);
8 | setupMirage(hooks);
9 |
10 | test("exists", async function (assert) {
11 | const service = this.owner.lookup("service:tracking");
12 | await settled();
13 | assert.ok(service);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/unit/services/unverified-reports-test.js:
--------------------------------------------------------------------------------
1 | import { settled } from "@ember/test-helpers";
2 | import { setupMirage } from "ember-cli-mirage/test-support";
3 | import { setupTest } from "ember-qunit";
4 | import { module, test } from "qunit";
5 |
6 | module("Unit | Service | unverified reports", function (hooks) {
7 | setupTest(hooks);
8 | setupMirage(hooks);
9 |
10 | test("exists", async function (assert) {
11 | const service = this.owner.lookup("service:unverified-reports");
12 | await settled();
13 | assert.ok(service);
14 | });
15 | });
16 |
--------------------------------------------------------------------------------
/tests/unit/sso-login/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | sso-login", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("it exists", function (assert) {
8 | const route = this.owner.lookup("route:sso-login");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/statistics/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | statistics", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:statistics");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/statistics/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | statistics", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:statistics");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/transforms/django-date-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import moment from "moment";
3 | import { module, test } from "qunit";
4 |
5 | module("Unit | Transform | django date", function (hooks) {
6 | setupTest(hooks);
7 |
8 | test("serializes", function (assert) {
9 | const transform = this.owner.lookup("transform:django-date");
10 |
11 | const result = transform.serialize(
12 | moment({
13 | y: 2017,
14 | M: 2, // moments months are zerobased
15 | d: 11,
16 | })
17 | );
18 |
19 | assert.strictEqual(result, "2017-03-11");
20 | });
21 |
22 | test("deserializes", function (assert) {
23 | const transform = this.owner.lookup("transform:django-date");
24 |
25 | assert.notOk(transform.deserialize(""));
26 | assert.notOk(transform.deserialize(null));
27 |
28 | const result = transform.deserialize("2017-03-11");
29 |
30 | assert.strictEqual(result.year(), 2017);
31 | assert.strictEqual(result.month(), 2); // moments months are zerobased
32 | assert.strictEqual(result.date(), 11);
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/unit/transforms/django-workdays-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Transform | django workdays", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("serializes", function (assert) {
8 | const transform = this.owner.lookup("transform:django-workdays");
9 |
10 | const result = transform.serialize([1, 2, 3, 4, 5]);
11 |
12 | assert.deepEqual(result, ["1", "2", "3", "4", "5"]);
13 | });
14 |
15 | test("deserializes", function (assert) {
16 | const transform = this.owner.lookup("transform:django-workdays");
17 |
18 | const result = transform.deserialize(["1", "2", "3", "4", "5"]);
19 |
20 | assert.deepEqual(result, [1, 2, 3, 4, 5]);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | users/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:users/edit");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/absence-credits/edit/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Controller | users/edit/credits/absence credits/edit",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const controller = this.owner.lookup(
11 | "controller:users/edit/credits/absence-credits/edit"
12 | );
13 | assert.ok(controller);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/absence-credits/edit/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Route | users/edit/credits/absence credits/edit",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const route = this.owner.lookup(
11 | "route:users/edit/credits/absence-credits/edit"
12 | );
13 | assert.ok(route);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/absence-credits/new/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Route | users/edit/credits/absence credits/new",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const route = this.owner.lookup(
11 | "route:users/edit/credits/absence-credits/new"
12 | );
13 | assert.ok(route);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/index/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | users/edit/credits/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:users/edit/credits/index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/index/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/edit/credits/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/edit/credits/index");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/overtime-credits/edit/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Controller | users/edit/credits/overtime credits/edit",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const controller = this.owner.lookup(
11 | "controller:users/edit/credits/overtime-credits/edit"
12 | );
13 | assert.ok(controller);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/overtime-credits/edit/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Route | users/edit/credits/overtime credits/edit",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const route = this.owner.lookup(
11 | "route:users/edit/credits/overtime-credits/edit"
12 | );
13 | assert.ok(route);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/overtime-credits/new/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module(
5 | "Unit | Route | users/edit/credits/overtime credits/new",
6 | function (hooks) {
7 | setupTest(hooks);
8 |
9 | test("exists", function (assert) {
10 | const route = this.owner.lookup(
11 | "route:users/edit/credits/overtime-credits/edit"
12 | );
13 | assert.ok(route);
14 | });
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/credits/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/edit/credits", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/edit/credits");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/index/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | users/edit/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:users/edit/index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/index/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/edit/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/edit/index");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/responsibilities/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | users/edit/responsibilities", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup(
9 | "controller:users/edit/responsibilities"
10 | );
11 | assert.ok(controller);
12 | });
13 | });
14 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/responsibilities/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/edit/responsibilities", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/edit/responsibilities");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/edit/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/edit", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/edit");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/index/controller-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Controller | users/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const controller = this.owner.lookup("controller:users/index");
9 | assert.ok(controller);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/index/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users/index", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users/index");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/users/route-test.js:
--------------------------------------------------------------------------------
1 | import { setupTest } from "ember-qunit";
2 | import { module, test } from "qunit";
3 |
4 | module("Unit | Route | users", function (hooks) {
5 | setupTest(hooks);
6 |
7 | test("exists", function (assert) {
8 | const route = this.owner.lookup("route:users");
9 | assert.ok(route);
10 | });
11 | });
12 |
--------------------------------------------------------------------------------
/tests/unit/utils/query-params-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from "qunit";
2 | import {
3 | serializeQueryParams,
4 | underscoreQueryParams,
5 | filterQueryParams,
6 | } from "timed/utils/query-params";
7 |
8 | module("Unit | Utility | query params", function () {
9 | test("can serialize query params", function (assert) {
10 | const params = { foo: 10 };
11 | const qp = {
12 | foo: {
13 | serialize: (val) => val * 10,
14 | },
15 | };
16 |
17 | const result = serializeQueryParams(params, qp);
18 |
19 | assert.strictEqual(result.foo, 100);
20 | });
21 |
22 | test("can underline query params", function (assert) {
23 | const params = { fooBar: 10, "baz-x": 10 };
24 |
25 | const result = underscoreQueryParams(params);
26 |
27 | assert.deepEqual(Object.keys(result), ["foo_bar", "baz_x"]);
28 | });
29 |
30 | test("can filter params", function (assert) {
31 | const params = { foo: 10, bar: 10, baz: 10 };
32 | const result = filterQueryParams(params, "foo", "bar");
33 |
34 | assert.deepEqual(result, { baz: 10 });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/tests/unit/utils/url-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from "qunit";
2 | import { cleanParams, toQueryString } from "timed/utils/url";
3 |
4 | module("Unit | Utility | url", function () {
5 | test("can clean params", function (assert) {
6 | const params = {
7 | 1: "",
8 | 2: null,
9 | 3: undefined,
10 | 4: 0,
11 | 5: "test",
12 | };
13 |
14 | const result = cleanParams(params);
15 |
16 | assert.deepEqual(result, {
17 | 1: "",
18 | 4: 0,
19 | 5: "test",
20 | });
21 | });
22 |
23 | test("can convert params to a query string", function (assert) {
24 | const params = {
25 | foo: "",
26 | bar: 0,
27 | baz: "test",
28 | };
29 |
30 | const result = toQueryString(params);
31 |
32 | assert.strictEqual(result, "foo=&bar=0&baz=test");
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/tests/unit/validators/null-or-not-blank-test.js:
--------------------------------------------------------------------------------
1 | import { module, test } from "qunit";
2 | import validateNullOrNotBlank from "timed/validators/null-or-not-blank";
3 |
4 | module("Unit | Validator | null or not blank", function () {
5 | test("works", function (assert) {
6 | assert.true(validateNullOrNotBlank()("key", "test"));
7 | assert.true(validateNullOrNotBlank()("key", null));
8 | assert.strictEqual(typeof validateNullOrNotBlank()("key", ""), "string");
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adfinis/timed-frontend/a91a9595042c81bd618d23bcf4303bd2ea80195d/vendor/.gitkeep
--------------------------------------------------------------------------------