├── .browserslistrc ├── .editorconfig ├── .gitignore ├── .huskyrc.json ├── .lintstagedrc.json ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── .stylelintrc.json ├── .travis.yml ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── eslint.config.mjs ├── gulpfile.js ├── package.json ├── projects └── ngx-datetime-range-picker │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── karma.conf.js │ ├── ng-package.json │ ├── node_modules │ └── .yarn-integrity │ ├── package.json │ ├── src │ ├── lib │ │ ├── interfaces │ │ │ └── index.ts │ │ ├── material │ │ │ └── material.module.ts │ │ ├── ngx-datetime-range-picker.component.html │ │ ├── ngx-datetime-range-picker.component.scss │ │ ├── ngx-datetime-range-picker.component.spec.ts │ │ ├── ngx-datetime-range-picker.component.ts │ │ ├── ngx-datetime-range-picker.constants.ts │ │ ├── ngx-datetime-range-picker.module.ts │ │ ├── ngx-datetime-range-picker.service.spec.ts │ │ ├── ngx-datetime-range-picker.service.ts │ │ ├── ngx-datetime-range-picker.utils.ts │ │ └── pipes │ │ │ └── objNgFor.pipe.ts │ ├── public_api.ts │ └── test.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── tsconfig.spec.json │ └── yarn.lock ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.config.ts │ ├── app.module.ts │ └── app.theme.scss ├── assets │ └── .gitkeep ├── common │ ├── page.component.ts │ └── services │ │ ├── app.init.service.ts │ │ ├── cache.service.ts │ │ ├── hash.service.ts │ │ ├── injector.service.ts │ │ ├── shared.service.ts │ │ └── util.service.ts ├── config │ ├── config.base.ts │ ├── config.dev.ts │ ├── config.dist.ts │ ├── config.prod.ts │ ├── config.ts │ └── constants.ts ├── favicon.ico ├── htaccess.dist ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── tslint.json ├── tsconfig.json ├── tslint.json └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | IE 9-11 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /projects/ngx-datetime-range-picker/dist/ 6 | /tmp 7 | /out-tsc 8 | .nyc_output 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events.json 15 | speed-measure-plugin.json 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | !.vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /libpeerconnection.log 37 | npm-debug.log 38 | yarn-error.log 39 | testem.log 40 | /typings 41 | 42 | # System Files 43 | .DS_Store 44 | Thumbs.db 45 | 46 | # Coverage 47 | /coverage 48 | /projects/coverage 49 | 50 | tslint-to-eslint-config.log 51 | .angular/ 52 | src/htaccess.dist 53 | .vs/ 54 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged -c .lintstagedrc.json" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": ["yarn pretty-quick-current-branch", "yarn lint:dev", "git add"], 3 | "*.css": ["yarn pretty-quick-current-branch", "yarn stylelint-current-branch", "git add"], 4 | "*.scss": ["yarn pretty-quick-current-branch", "yarn stylelint-current-branch --syntax=scss", "git add"], 5 | "*.{png,jpeg,jpg,gif,svg}": ["imagemin-lint-staged", "git add"], 6 | "*.{js,html,json,md}": ["yarn pretty-quick-current-branch", "git add"] 7 | } 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | v18.17.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | coverage/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "jsxBracketSameLine": true, 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /.stylelintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "block-no-empty": null, 4 | "color-no-invalid-hex": true, 5 | "comment-empty-line-before": [ 6 | "always", 7 | { 8 | "ignore": ["stylelint-commands", "after-comment"] 9 | } 10 | ], 11 | "declaration-colon-space-after": "always", 12 | "indentation": [ 13 | 2, 14 | { 15 | "except": ["value"] 16 | } 17 | ], 18 | "max-empty-lines": 2, 19 | "rule-empty-line-before": [ 20 | "always", 21 | { 22 | "except": ["first-nested"], 23 | "ignore": ["after-comment"] 24 | } 25 | ] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | sudo: false 3 | 4 | language: node_js 5 | node_js: 6 | - "10" 7 | addons: 8 | chrome: stable 9 | 10 | cache: 11 | directories: 12 | - ./node_modules 13 | 14 | install: 15 | - npm run setup 16 | 17 | script: 18 | - npm run test-ci 19 | 20 | notifications: 21 | email: 22 | on_failure: change 23 | on_success: change 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bhavin Patel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ngx-datetime-range-picker 2 | 3 | > Ngx Date time range picker with daily, weekly, monthly, quarterly & yearly levels 4 | 5 | [![Build Status](https://travis-ci.org/BhavinPatel04/ngx-datetime-range-picker.svg?branch=master)](https://travis-ci.org/BhavinPatel04/ngx-datetime-range-picker) 6 | [![npm version](https://badge.fury.io/js/ngx-datetime-range-picker.svg)](https://badge.fury.io/js/ngx-datetime-range-picker) 7 | 8 | This plugin uses bootstrap and moment.js 9 | 10 | [Demo](https://bhavinpatel04.github.io/ngx-datetime-range-picker/) 11 | 12 | ## React version 13 | 14 | [reactjs-datetime-range-picker](https://github.com/BhavinPatel04/reactjs-datetime-range-picker) 15 | 16 | ## Installation 17 | 18 | #### Install the plugin through npm: 19 | 20 | ``` 21 | npm install ngx-datetime-range-picker --save 22 | ``` 23 | 24 | #### Install the plugin through yarn: 25 | 26 | ``` 27 | yarn add ngx-datetime-range-picker 28 | ``` 29 | 30 | #### import _NgxDatetimeRangePickerModule_ in your module: 31 | 32 | ```typescript 33 | import { BrowserModule } from "@angular/platform-browser"; 34 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 35 | ... 36 | import { FormsModule } from "@angular/forms"; 37 | import { NgxDatetimeRangePickerModule } from "ngx-datetime-range-picker"; 38 | import { AppComponent } from "./app.component"; 39 | 40 | @NgModule({ 41 | imports: [BrowserModule, BrowserAnimationsModule, ... , FormsModule, NgxDatetimeRangePickerModule.forRoot()], 42 | declarations: [AppComponent], 43 | bootstrap: [AppComponent] 44 | }) 45 | export class AppModule {} 46 | ``` 47 | 48 | #### Tell ngx-datetime-range-picker which theme to use:
49 | 50 | ##### SCSS 51 | 52 | Add below config in your `style.scss`/`some-cool-name-theme.scss`, which has material theme (or not) and is imported in your `angular.json` 53 | 54 | ``` 55 | // Top of the file 56 | @use "ngx-datetime-range-picker/ngx-datetime-range-picker.component.scss" as ndtrp; 57 | 58 | // You would need to include the themes for below components if you are not including them already 59 | @include mat.form-field-theme($your-app-theme); 60 | @include mat.icon-theme($your-app-theme); 61 | @include mat.button-theme($your-app-theme); 62 | @include mat.select-theme($your-app-theme); 63 | 64 | @include ndtrp.ngx-datetime-range-picker-theme($your-app-theme); 65 | ``` 66 | 67 | If you _don't_ have a theme and want to use library's default theme 68 | 69 | ``` 70 | // Top of the file 71 | @use "ngx-datetime-range-picker/ngx-datetime-range-picker.component.scss" as ndtrp; 72 | 73 | // You would need to include the themes for below components if you are not including them already 74 | @include mat.form-field-theme(ndtrp.$ndtrp-app-theme); 75 | @include mat.icon-theme(ndtrp.$ndtrp-app-theme); 76 | @include mat.button-theme(ndtrp.$ndtrp-app-theme); 77 | @include mat.select-theme(ndtrp.$ndtrp-app-theme); 78 | 79 | @include ndtrp.ngx-datetime-range-picker-theme(); 80 | ``` 81 | 82 | _Note_: _Themes for components are not included in the library to avoid duplicate styles error_ 83 | 84 | ##### CSS 85 | 86 | Add below config in your `style.css`/`some-cool-name-theme.css` and is imported in your `angular.json` 87 | 88 | ``` 89 | @import "ngx-datetime-range-picker/ngx-datetime-range-picker.theme.css"; 90 | ``` 91 | 92 | _Note_: Make sure whichever file you are adding this to should be imported in `angular.json` 93 | 94 | ## Usage example 95 | 96 | #### Html: 97 | 98 | ```html 99 | 107 | 108 | ``` 109 | 110 | #### Typescript: 111 | 112 | ```typescript 113 | selectedDate: { 114 | daily: { 115 | startDate: "2018-10-13", 116 | endDate: "2018-10-19", 117 | }, 118 | weekly: { 119 | startDate: "2018-10-13", 120 | endDate: "2018-10-19", 121 | }, 122 | monthly: { 123 | startDate: "2018-10-13", 124 | endDate: "2018-10-19", 125 | }, 126 | quarterly: { 127 | startDate: "2018-10-13", 128 | endDate: "2018-10-19", 129 | }, 130 | yearly: { 131 | startDate: "2018-10-13", 132 | endDate: "2018-10-19", 133 | } 134 | }; 135 | ``` 136 | 137 | ## Options 138 | 139 | | Option | Type | Description | 140 | | --------- | ------ | ------------------------------------------- | 141 | | dateArray | Array | Only the dates in the array will be enabled | 142 | | startDate | string | Start date | 143 | | endDate | string | End date | 144 | | minDate | string | Min date | 145 | | maxDate | string | Max date | 146 | | startTime | string | Start time (only for timepicker) | 147 | | endTime | string | End time (only for timepicker) | 148 | | minTime | string | Min time (only for timepicker) | 149 | | maxTime | string | Max time (only for timepicker) | 150 | 151 | ## Settings 152 | 153 | | Setting | Type | Default | Description | 154 | | ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | 155 | | type | string | 'daily' | type (daily | weekly | monthly | quarterly | yearly) | 156 | | timePicker | boolean | false | enable/disable timepicker | 157 | | inputDateFormat | string | "YYYY-MM-DD" | input date format | 158 | | viewDateFormat | string | "YYYY-MM-DD" | date format to view the date in | 159 | | outputDateFormat | string | "YYYY-MM-DD" | date format in which change event will return the date in | 160 | | singleDatePicker | boolean | false | enable/disable single date picker | 161 | | componentDisabled | string | false | enable/disable component | 162 | | placeholder | string | "Select Date" | placeholder/title of the component | 163 | | showRowNumber | boolean | true | hide/show week numers for daily | 164 | | availableRanges | Object | {"Last 7 Days": {startDate: inputDateFormat, endDate: inputDateFormat}, "Last 30 days": {...}, "Last 90 days": {...}} | ranges to show | 165 | | showRanges | boolean | true | hide/show ranges | 166 | | disableWeekends | boolean | false | enable/disable weekends | 167 | | disableWeekdays | boolean | false | enable/disable weekdays | 168 | | displayBeginDate | boolean | false | show begin date in input | 169 | | displayEndDate | boolean | false | show end date in input | 170 | 171 | ## [License](https://github.com/BhavinPatel04/ngx-datetime-range-picker/blob/master/LICENSE) 172 | 173 | MIT 174 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ngx-barebone-app": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": { 12 | "@schematics/angular:component": { 13 | "style": "scss" 14 | } 15 | }, 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:application", 19 | "options": { 20 | "aot": true, 21 | "outputPath": { 22 | "base": "dist", 23 | "browser": "" 24 | }, 25 | "index": "src/index.html", 26 | "polyfills": ["src/polyfills.ts"], 27 | "tsConfig": "src/tsconfig.app.json", 28 | "assets": ["src/favicon.ico", "src/assets"], 29 | "styles": ["@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss", "src/app/app.theme.scss"], 30 | "scripts": [], 31 | "stylePreprocessorOptions": { 32 | "includePaths": ["./node_modules/"] 33 | }, 34 | "browser": "src/main.ts" 35 | }, 36 | "configurations": { 37 | "dev": { 38 | "budgets": [ 39 | { 40 | "type": "anyComponentStyle", 41 | "maximumWarning": "6kb" 42 | } 43 | ], 44 | "fileReplacements": [ 45 | { 46 | "replace": "src/config/config.ts", 47 | "with": "src/config/config.dev.ts" 48 | } 49 | ] 50 | }, 51 | "production": { 52 | "fileReplacements": [ 53 | { 54 | "replace": "src/config/config.ts", 55 | "with": "src/config/config.prod.ts" 56 | } 57 | ], 58 | "optimization": true, 59 | "outputHashing": "all", 60 | "sourceMap": false, 61 | "namedChunks": false, 62 | "aot": true, 63 | "extractLicenses": true, 64 | "budgets": [ 65 | { 66 | "type": "initial", 67 | "maximumWarning": "2mb", 68 | "maximumError": "5mb" 69 | }, 70 | { 71 | "type": "anyComponentStyle", 72 | "maximumWarning": "6kb" 73 | } 74 | ] 75 | }, 76 | "test": { 77 | "budgets": [ 78 | { 79 | "type": "anyComponentStyle", 80 | "maximumWarning": "6kb" 81 | } 82 | ], 83 | "fileReplacements": [ 84 | { 85 | "replace": "src/config/config.ts", 86 | "with": "src/config/config.test.ts" 87 | } 88 | ] 89 | } 90 | } 91 | }, 92 | "serve": { 93 | "builder": "@angular-devkit/build-angular:dev-server", 94 | "options": { 95 | "buildTarget": "ngx-barebone-app:build" 96 | }, 97 | "configurations": { 98 | "dev": { 99 | "buildTarget": "ngx-barebone-app:build:dev" 100 | }, 101 | "production": { 102 | "buildTarget": "ngx-barebone-app:build:production" 103 | }, 104 | "test": { 105 | "buildTarget": "ngx-barebone-app:build:test" 106 | } 107 | } 108 | }, 109 | "extract-i18n": { 110 | "builder": "@angular-devkit/build-angular:extract-i18n", 111 | "options": { 112 | "buildTarget": "ngx-barebone-app:build" 113 | } 114 | }, 115 | "test": { 116 | "builder": "@angular-devkit/build-angular:karma", 117 | "options": { 118 | "main": "src/test.ts", 119 | "polyfills": "src/polyfills.ts", 120 | "tsConfig": "src/tsconfig.spec.json", 121 | "karmaConfig": "src/karma.conf.js", 122 | "styles": ["@angular/material/prebuilt-themes/azure-blue.css", "src/styles.scss", "src/app/app.theme.scss"], 123 | "scripts": [], 124 | "assets": ["src/favicon.ico", "src/assets"], 125 | "codeCoverageExclude": ["src/common/**/*", "src/config/**/*", "src/test.ts", "src/app/app.config.ts"] 126 | } 127 | }, 128 | "lint": { 129 | "builder": "@angular-devkit/build-angular:tslint", 130 | "options": { 131 | "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], 132 | "exclude": ["**/node_modules/**"] 133 | } 134 | } 135 | } 136 | }, 137 | "ngx-barebone-app-e2e": { 138 | "root": "e2e/", 139 | "projectType": "application", 140 | "prefix": "", 141 | "architect": { 142 | "e2e": { 143 | "builder": "@angular-devkit/build-angular:protractor", 144 | "options": { 145 | "protractorConfig": "e2e/protractor.conf.js", 146 | "devServerTarget": "ngx-barebone-app:serve" 147 | }, 148 | "configurations": { 149 | "production": { 150 | "devServerTarget": "ngx-barebone-app:serve:production" 151 | } 152 | } 153 | }, 154 | "lint": { 155 | "builder": "@angular-devkit/build-angular:tslint", 156 | "options": { 157 | "tsConfig": "e2e/tsconfig.e2e.json", 158 | "exclude": ["**/node_modules/**"] 159 | } 160 | } 161 | } 162 | }, 163 | "ngx-datetime-range-picker": { 164 | "root": "projects/ngx-datetime-range-picker", 165 | "sourceRoot": "projects/ngx-datetime-range-picker/src", 166 | "projectType": "library", 167 | "prefix": "lib", 168 | "architect": { 169 | "build": { 170 | "builder": "@angular-devkit/build-angular:ng-packagr", 171 | "options": { 172 | "tsConfig": "projects/ngx-datetime-range-picker/tsconfig.lib.json", 173 | "project": "projects/ngx-datetime-range-picker/ng-package.json" 174 | }, 175 | "configurations": { 176 | "production": { 177 | "tsConfig": "projects/ngx-datetime-range-picker/tsconfig.lib.prod.json" 178 | } 179 | } 180 | }, 181 | "test": { 182 | "builder": "@angular-devkit/build-angular:karma", 183 | "options": { 184 | "main": "projects/ngx-datetime-range-picker/src/test.ts", 185 | "tsConfig": "projects/ngx-datetime-range-picker/tsconfig.spec.json", 186 | "karmaConfig": "projects/ngx-datetime-range-picker/karma.conf.js" 187 | } 188 | }, 189 | "lint": { 190 | "builder": "@angular-devkit/build-angular:tslint", 191 | "options": { 192 | "tsConfig": [ 193 | "projects/ngx-datetime-range-picker/tsconfig.lib.json", 194 | "projects/ngx-datetime-range-picker/tsconfig.spec.json" 195 | ], 196 | "exclude": ["**/node_modules/**"] 197 | } 198 | } 199 | } 200 | } 201 | }, 202 | "cli": { 203 | "analytics": false 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require("jasmine-spec-reporter"); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: ["./src/**/*.e2e-spec.ts"], 9 | capabilities: { 10 | browserName: "chrome" 11 | }, 12 | directConnect: true, 13 | baseUrl: "http://localhost:4200/", 14 | framework: "jasmine", 15 | jasmineNodeOpts: { 16 | showColors: true, 17 | defaultTimeoutInterval: 30000, 18 | print: function() {} 19 | }, 20 | onPrepare() { 21 | require("ts-node").register({ 22 | project: require("path").join(__dirname, "./tsconfig.e2e.json") 23 | }); 24 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from "./app.po"; 2 | 3 | describe("workspace-project App", () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it("should display welcome message", () => { 11 | page.navigateTo(); 12 | // expect(page.getTitleText()).toEqual("Welcome to ngx-barebone-app!"); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from "protractor"; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get("/"); 6 | } 7 | 8 | getTitleText() { 9 | return element(by.css("app-root h1")).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "amd", 6 | "target": "es2015", 7 | "types": ["jasmine", "jasminewd2", "node"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import globals from "globals"; 2 | import pluginJs from "@eslint/js"; 3 | import tseslint from "typescript-eslint"; 4 | 5 | /** @type {import('eslint').Linter.Config[]} */ 6 | export default [ 7 | { files: ["**/*.ts"] }, 8 | { languageOptions: { globals: globals.browser } }, 9 | pluginJs.configs.recommended, 10 | { 11 | ignores: ["node_modules", "**/*.js", "*.mjs", "*.cjs", "**/dist"] 12 | }, 13 | ...tseslint.configs.recommended, 14 | { 15 | rules: { 16 | "@typescript-eslint/no-explicit-any": "off" 17 | } 18 | } 19 | ]; 20 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const gulp = require("gulp"); 3 | const rename = require("gulp-rename"); 4 | 5 | gulp.task( 6 | "init", 7 | gulp.series((done) => { 8 | fs.stat("src/config/config.ts", (err, stat) => { 9 | // copy when file not exists 10 | if (err != null) { 11 | console.log("Coping `config.dist.ts` to `config.ts`"); 12 | gulp 13 | .src("src/config/config.dist.ts") 14 | .pipe(rename("config.ts")) 15 | .pipe(gulp.dest("src/config")); 16 | } 17 | done(); 18 | }); 19 | }) 20 | ); 21 | 22 | gulp.task( 23 | "build", 24 | gulp.series(() => { 25 | return gulp 26 | .src("src/htaccess.dist") 27 | .pipe(rename(".htaccess")) 28 | .pipe(gulp.dest("dist")); 29 | }) 30 | ); 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-barebone-app", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/BhavinPatel04/ngx-barebone-app#readme", 5 | "bugs": "https://github.com/BhavinPatel04/ngx-barebone-app/issues", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/BhavinPatel04/ngx-barebone-app" 9 | }, 10 | "author": "Bhavin Patel", 11 | "license": "MIT", 12 | "keywords": [ 13 | "angular", 14 | "angular2", 15 | "angular4", 16 | "angular5", 17 | "angular6", 18 | "angular7", 19 | "angular8", 20 | "angular9", 21 | "angular10", 22 | "ng2", 23 | "ng4", 24 | "ng5", 25 | "ng7", 26 | "ng8", 27 | "ng9", 28 | "ng10", 29 | "ngx", 30 | "boilerplate", 31 | "barebone", 32 | "datetime-range-picker", 33 | "datetime-range", 34 | "datetime", 35 | "date-time-range-picker", 36 | "date-time-range", 37 | "date-time", 38 | "date-range-picker", 39 | "date-range", 40 | "date", 41 | "time-range-picker", 42 | "time-range", 43 | "time", 44 | "ngx-datetime-range-picker", 45 | "ngx-datetime-range", 46 | "ngx-datetime", 47 | "ngx-date-time-range-picker", 48 | "ngx-date-time-range", 49 | "ngx-date-time", 50 | "ngx-date-range-picker", 51 | "ngx-date-range", 52 | "ngx-date", 53 | "ngx-time-range-picker", 54 | "ngx-time-range", 55 | "ngx-time" 56 | ], 57 | "scripts": { 58 | "setup": "yarn install && gulp init", 59 | "ng": "ng", 60 | "start": "ng serve", 61 | "dev": "lite-server --baseDir=dist", 62 | "build": "ng build", 63 | "build-dev": "ng build --configuration dev --output-hashing=all && gulp build", 64 | "build-prod": "node --max-old-space-size=4076 node_modules/@angular/cli/bin/ng build --configuration production && gulp build", 65 | "package-lib:prod": "ng build ngx-datetime-range-picker --configuration production && yarn build-lib-css", 66 | "package-lib": "ng-packagr -p ./projects/ngx-datetime-range-picker/ng-package.json && yarn build-lib-css", 67 | "build-lib-css": "sass --load-path=./node_modules ./projects/ngx-datetime-range-picker/dist/ngx-datetime-range-picker.component.scss ./projects/ngx-datetime-range-picker/dist/ngx-datetime-range-picker.component.css", 68 | "test": "ng test", 69 | "lint": "eslint", 70 | "lint:dev": "eslint --fix", 71 | "e2e": "ng e2e", 72 | "gh-pages": "ng build --configuration production --base-href=\"/ngx-datetime-range-picker/\" && ngh --dir dist", 73 | "format": "prettier \"**/*.{ts,js,html,css,scss,json,md}\" --write", 74 | "check-format": "prettier \"**/*.{ts,js,html,css,scss,json,md}\" --write -l", 75 | "current-branch": "$(git branch | grep \\* | cut -d ' ' -f2)", 76 | "pretty-quick-current-branch": "pretty-quick --staged --branch yarn current-branch", 77 | "stylelint-current-branch": "stylelint yarn current-branch --fix", 78 | "test-ci": "yarn lint && yarn check-format && yarn test-generate-coverage", 79 | "test-generate-coverage": "ng test ngx-datetime-range-picker -- --no-watch --no-progress --browsers=ChromeHeadlessCI --code-coverage", 80 | "coveralls": "nyc report --reporter=text-lcov | coveralls", 81 | "codecov": "nyc report --reporter=text-lcov > coverage.lcov && codecov" 82 | }, 83 | "private": false, 84 | "dependencies": { 85 | "@angular/animations": "~19.0.6", 86 | "@angular/cdk": "^19.0.5", 87 | "@angular/common": "~19.0.6", 88 | "@angular/compiler": "~19.0.6", 89 | "@angular/core": "~19.0.6", 90 | "@angular/forms": "~19.0.6", 91 | "@angular/material": "^19.0.5", 92 | "@angular/platform-browser": "~19.0.6", 93 | "@angular/platform-browser-dynamic": "~19.0.6", 94 | "core-js": "^2.5.4", 95 | "global": "^4.4.0", 96 | "gulp": "^4.0.0", 97 | "gulp-rename": "^1.4.0", 98 | "node-gyp": "^11.0.0", 99 | "rxjs": "~6.6.3", 100 | "tar": "^4.4.10", 101 | "tslib": "^2.0.0", 102 | "zone.js": "~0.15.0" 103 | }, 104 | "devDependencies": { 105 | "@angular-devkit/build-angular": "^19.0.7", 106 | "@angular-devkit/build-ng-packagr": "~0.1002.0", 107 | "@angular/cli": "^19.0.7", 108 | "@angular/compiler-cli": "^19.0.6", 109 | "@angular/language-service": "~19.0.6", 110 | "@eslint/compat": "^1.2.5", 111 | "@eslint/eslintrc": "^3.2.0", 112 | "@eslint/js": "^9.18.0", 113 | "@types/jasmine": "~2.8.8", 114 | "@types/jasminewd2": "~2.0.3", 115 | "@types/moment": "^2.13.0", 116 | "@types/node": "^12.11.1", 117 | "@typescript-eslint/eslint-plugin-tslint": "^7.0.1", 118 | "angular-eslint": "19.0.2", 119 | "codelyzer": "^5.1.2", 120 | "eslint": "^9.18.0", 121 | "eslint-config-prettier": "^9.1.0", 122 | "eslint-plugin-import": "^2.29.1", 123 | "eslint-plugin-jsdoc": "^48.1.0", 124 | "globals": "^15.14.0", 125 | "husky": "^1.3.1", 126 | "jasmine-core": "~3.5.0", 127 | "jasmine-spec-reporter": "~5.0.0", 128 | "jssha": "^2.3.1", 129 | "karma": "~6.4.4", 130 | "karma-chrome-launcher": "~3.1.0", 131 | "karma-coverage-istanbul-reporter": "~3.0.2", 132 | "karma-jasmine": "~4.0.0", 133 | "karma-jasmine-html-reporter": "^1.5.0", 134 | "lint-staged": "^15.2.2", 135 | "lite-server": "^2.6.1", 136 | "moment": "^2.23.0", 137 | "ng-packagr": "^19.0.1", 138 | "ngx-datetime-range-picker": "^5.0.0", 139 | "node-sass": "^9.0.0", 140 | "prettier": "^1.15.3", 141 | "pretty-quick": "^1.8.0", 142 | "protractor": "~7.0.0", 143 | "stylelint": "^9.9.0", 144 | "ts-node": "~7.0.0", 145 | "tslint": "^6.1.3", 146 | "typescript": "5.6.3", 147 | "typescript-eslint": "^8.19.1" 148 | }, 149 | "resolutions": { 150 | "json5": "^2.2.3", 151 | "qs": "^6.11.2" 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | 👋 Hi! This file was autogenerated by tslint-to-eslint-config. 3 | https://github.com/typescript-eslint/tslint-to-eslint-config 4 | 5 | It represents the closest reasonable ESLint configuration to this 6 | project's original TSLint configuration. 7 | 8 | We recommend eventually switching this configuration to extend from 9 | the recommended rulesets in typescript-eslint. 10 | https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md 11 | 12 | Happy linting! 💖 13 | */ 14 | module.exports = { 15 | env: { 16 | browser: true, 17 | es6: true 18 | }, 19 | extends: ["prettier"], 20 | parser: "@typescript-eslint/parser", 21 | parserOptions: { 22 | project: "tsconfig.json", 23 | sourceType: "module" 24 | }, 25 | plugins: [ 26 | "@angular-eslint/eslint-plugin", 27 | "eslint-plugin-import", 28 | "eslint-plugin-jsdoc", 29 | "@typescript-eslint", 30 | "@typescript-eslint/tslint" 31 | ], 32 | root: true, 33 | rules: { 34 | "@angular-eslint/component-class-suffix": "error", 35 | "@angular-eslint/component-selector": [ 36 | "error", 37 | { 38 | type: "element", 39 | prefix: "", 40 | style: "kebab-case" 41 | } 42 | ], 43 | "@angular-eslint/directive-class-suffix": "error", 44 | "@angular-eslint/directive-selector": [ 45 | "error", 46 | { 47 | type: "attribute", 48 | prefix: "", 49 | style: "camelCase" 50 | } 51 | ], 52 | "@angular-eslint/no-host-metadata-property": "error", 53 | "@angular-eslint/no-input-rename": "error", 54 | "@angular-eslint/no-inputs-metadata-property": "error", 55 | "@angular-eslint/no-output-on-prefix": "error", 56 | "@angular-eslint/no-output-rename": "error", 57 | "@angular-eslint/no-outputs-metadata-property": "error", 58 | "@angular-eslint/use-lifecycle-interface": "error", 59 | "@angular-eslint/use-pipe-transform-interface": "error", 60 | "@typescript-eslint/consistent-type-definitions": "error", 61 | "@typescript-eslint/dot-notation": "off", 62 | "@typescript-eslint/explicit-member-accessibility": [ 63 | "off", 64 | { 65 | accessibility: "explicit" 66 | } 67 | ], 68 | "@typescript-eslint/indent": "error", 69 | "@typescript-eslint/member-delimiter-style": [ 70 | "error", 71 | { 72 | multiline: { 73 | delimiter: "semi", 74 | requireLast: true 75 | }, 76 | singleline: { 77 | delimiter: "semi", 78 | requireLast: false 79 | } 80 | } 81 | ], 82 | "@typescript-eslint/member-ordering": "error", 83 | "@typescript-eslint/naming-convention": [ 84 | "error", 85 | { 86 | selector: "variable", 87 | format: ["camelCase", "UPPER_CASE"], 88 | leadingUnderscore: "forbid", 89 | trailingUnderscore: "forbid" 90 | } 91 | ], 92 | "@typescript-eslint/no-empty-function": "off", 93 | "@typescript-eslint/no-empty-interface": "error", 94 | "@typescript-eslint/no-inferrable-types": [ 95 | "error", 96 | { 97 | ignoreParameters: true 98 | } 99 | ], 100 | "@typescript-eslint/no-misused-new": "error", 101 | "@typescript-eslint/no-non-null-assertion": "error", 102 | "@typescript-eslint/no-shadow": [ 103 | "error", 104 | { 105 | hoist: "all" 106 | } 107 | ], 108 | "@typescript-eslint/no-unused-expressions": "error", 109 | "@typescript-eslint/prefer-function-type": "error", 110 | "@typescript-eslint/quotes": ["error", "double"], 111 | "@typescript-eslint/semi": ["error", "always"], 112 | "@typescript-eslint/type-annotation-spacing": "error", 113 | "@typescript-eslint/unified-signatures": "error", 114 | "arrow-body-style": "error", 115 | "brace-style": ["error", "1tbs"], 116 | "constructor-super": "error", 117 | curly: "error", 118 | "dot-notation": "off", 119 | "eol-last": "error", 120 | eqeqeq: ["error", "smart"], 121 | "guard-for-in": "error", 122 | "id-denylist": "off", 123 | "id-match": "off", 124 | "import/no-deprecated": "warn", 125 | indent: "off", 126 | "jsdoc/no-types": "error", 127 | "max-len": [ 128 | "error", 129 | { 130 | code: 140 131 | } 132 | ], 133 | "no-bitwise": "error", 134 | "no-caller": "error", 135 | "no-console": [ 136 | "error", 137 | { 138 | allow: [ 139 | "log", 140 | "warn", 141 | "dir", 142 | "timeLog", 143 | "assert", 144 | "clear", 145 | "count", 146 | "countReset", 147 | "group", 148 | "groupEnd", 149 | "table", 150 | "dirxml", 151 | "error", 152 | "groupCollapsed", 153 | "Console", 154 | "profile", 155 | "profileEnd", 156 | "timeStamp", 157 | "context" 158 | ] 159 | } 160 | ], 161 | "no-debugger": "error", 162 | "no-empty": "off", 163 | "no-empty-function": "off", 164 | "no-eval": "error", 165 | "no-fallthrough": "error", 166 | "no-new-wrappers": "error", 167 | "no-restricted-imports": ["error", "rxjs/Rx"], 168 | "no-shadow": "off", 169 | "no-throw-literal": "error", 170 | "no-trailing-spaces": "error", 171 | "no-undef-init": "error", 172 | "no-underscore-dangle": "off", 173 | "no-unused-expressions": "off", 174 | "no-unused-labels": "error", 175 | "no-var": "error", 176 | "prefer-const": "error", 177 | quotes: "off", 178 | radix: "error", 179 | semi: "off", 180 | "spaced-comment": [ 181 | "error", 182 | "always", 183 | { 184 | markers: ["/"] 185 | } 186 | ], 187 | "@typescript-eslint/tslint/config": [ 188 | "error", 189 | { 190 | rules: { 191 | "import-spacing": true, 192 | whitespace: [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"] 193 | } 194 | } 195 | ] 196 | } 197 | }; 198 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bhavin Patel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/README.md: -------------------------------------------------------------------------------- 1 | # ngx-datetime-range-picker 2 | 3 | > Ngx Date time range picker with daily, weekly, monthly, quarterly & yearly levels 4 | 5 | [![Build Status](https://travis-ci.org/BhavinPatel04/ngx-datetime-range-picker.svg?branch=master)](https://travis-ci.org/BhavinPatel04/ngx-datetime-range-picker) 6 | [![npm version](https://badge.fury.io/js/ngx-datetime-range-picker.svg)](https://badge.fury.io/js/ngx-datetime-range-picker) 7 | 8 | This plugin uses bootstrap and moment.js 9 | 10 | Demo: https://bhavinpatel04.github.io/ngx-datetime-range-picker/ 11 | 12 | ## Installation 13 | 14 | #### Install the plugin from npm: 15 | 16 | ``` 17 | npm install ngx-datetime-range-picker --save 18 | ``` 19 | 20 | #### import _NgxDatetimeRangePickerModule_ in your module: 21 | 22 | ```typescript 23 | import { BrowserModule } from "@angular/platform-browser"; 24 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 25 | ... 26 | import { FormsModule } from "@angular/forms"; 27 | import { NgxDatetimeRangePickerModule } from "ngx-datetime-range-picker"; 28 | import { AppComponent } from "./app.component"; 29 | 30 | @NgModule({ 31 | imports: [BrowserModule, BrowserAnimationsModule, ... , FormsModule, NgxDatetimeRangePickerModule.forRoot()], 32 | declarations: [AppComponent], 33 | bootstrap: [AppComponent] 34 | }) 35 | export class AppModule {} 36 | ``` 37 | 38 | #### Tell ngx-datetime-range-picker which theme to use:
39 | 40 | ##### SCSS 41 | 42 | Add below config in your `style.scss`/`some-cool-name-theme.scss`, which has material theme (or not) and is imported in your `angular.json` 43 | 44 | ``` 45 | // Top of the file 46 | @use "ngx-datetime-range-picker/ngx-datetime-range-picker.component.scss" as ndtrp; 47 | 48 | // You would need to include the themes for below components if you are not including them already 49 | @include mat.form-field-theme($your-app-theme); 50 | @include mat.icon-theme($your-app-theme); 51 | @include mat.button-theme($your-app-theme); 52 | @include mat.select-theme($your-app-theme); 53 | 54 | @include ndtrp.ngx-datetime-range-picker-theme($your-app-theme); 55 | ``` 56 | 57 | If you _don't_ have a theme and want to use library's default theme 58 | 59 | ``` 60 | // Top of the file 61 | @use "ngx-datetime-range-picker/ngx-datetime-range-picker.component.scss" as ndtrp; 62 | 63 | // You would need to include the themes for below components if you are not including them already 64 | @include mat.form-field-theme(ndtrp.$ndtrp-app-theme); 65 | @include mat.icon-theme(ndtrp.$ndtrp-app-theme); 66 | @include mat.button-theme(ndtrp.$ndtrp-app-theme); 67 | @include mat.select-theme(ndtrp.$ndtrp-app-theme); 68 | 69 | @include ndtrp.ngx-datetime-range-picker-theme(); 70 | ``` 71 | 72 | _Note_: _Themes for components are not included in the library to avoid duplicate styles error_ 73 | 74 | ##### CSS 75 | 76 | Add below config in your `style.css`/`some-cool-name-theme.css` and is imported in your `angular.json` 77 | 78 | ``` 79 | @import "ngx-datetime-range-picker/ngx-datetime-range-picker.theme.css"; 80 | ``` 81 | 82 | _Note_: Make sure whichever file you are adding this to should be imported in `angular.json` 83 | 84 | ## Usage example 85 | 86 | #### Html: 87 | 88 | ```html 89 | 97 | 98 | ``` 99 | 100 | #### Typescript: 101 | 102 | ```typescript 103 | selectedDate: { 104 | daily: { 105 | startDate: "2018-10-13", 106 | endDate: "2018-10-19", 107 | }, 108 | weekly: { 109 | startDate: "2018-10-13", 110 | endDate: "2018-10-19", 111 | }, 112 | monthly: { 113 | startDate: "2018-10-13", 114 | endDate: "2018-10-19", 115 | }, 116 | quarterly: { 117 | startDate: "2018-10-13", 118 | endDate: "2018-10-19", 119 | }, 120 | yearly: { 121 | startDate: "2018-10-13", 122 | endDate: "2018-10-19", 123 | } 124 | }; 125 | ``` 126 | 127 | ## Options 128 | 129 | | Option | Type | Description | 130 | | --------- | ------ | ------------------------------------------- | 131 | | dateArray | Array | Only the dates in the array will be enabled | 132 | | startDate | string | Start date | 133 | | endDate | string | End date | 134 | | minDate | string | Min date | 135 | | maxDate | string | Max date | 136 | | startTime | string | Start time (only for timepicker) | 137 | | endTime | string | End time (only for timepicker) | 138 | | minTime | string | Min time (only for timepicker) | 139 | | maxTime | string | Max time (only for timepicker) | 140 | 141 | ## Settings 142 | 143 | | Setting | Type | Default | Description | 144 | | ----------------- | ------- | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | 145 | | type | string | 'daily' | type (daily | weekly | monthly | quarterly | yearly) | 146 | | timePicker | boolean | false | enable/disable timepicker | 147 | | inputDateFormat | string | "YYYY-MM-DD" | input date format | 148 | | viewDateFormat | string | "YYYY-MM-DD" | date format to view the date in | 149 | | outputDateFormat | string | "YYYY-MM-DD" | date format in which change event will return the date in | 150 | | singleDatePicker | boolean | false | enable/disable single date picker | 151 | | componentDisabled | string | false | enable/disable component | 152 | | placeholder | string | "Select Date" | placeholder/title of the component | 153 | | showRowNumber | boolean | true | hide/show week numers for daily | 154 | | availableRanges | Object | {"Last 7 Days": {startDate: inputDateFormat, endDate: inputDateFormat}, "Last 30 days": {...}, "Last 90 days": {...}} | ranges to show | 155 | | showRanges | boolean | true | hide/show ranges | 156 | | disableWeekends | boolean | false | enable/disable weekends | 157 | | disableWeekdays | boolean | false | enable/disable weekdays | 158 | | displayBeginDate | boolean | false | show begin date in input | 159 | | displayEndDate | boolean | false | show end date in input | 160 | 161 | ## [License](https://github.com/BhavinPatel04/ngx-datetime-range-picker/blob/master/LICENSE) 162 | 163 | MIT 164 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: "", 7 | frameworks: ["jasmine", "@angular-devkit/build-angular"], 8 | plugins: [ 9 | require("karma-jasmine"), 10 | require("karma-chrome-launcher"), 11 | require("karma-jasmine-html-reporter"), 12 | require("karma-coverage-istanbul-reporter"), 13 | require("@angular-devkit/build-angular/plugins/karma") 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require("path").join(__dirname, "../coverage"), 20 | reports: ["html", "lcovonly", "text-summary"], 21 | fixWebpackSourcePaths: true, 22 | thresholds: { 23 | statements: 0, 24 | lines: 0, 25 | branches: 0, 26 | functions: 0 27 | } 28 | }, 29 | reporters: ["progress", "kjhtml"], 30 | port: 9876, 31 | colors: true, 32 | logLevel: config.LOG_INFO, 33 | autoWatch: true, 34 | browsers: ["Chrome"], 35 | customLaunchers: { 36 | ChromeHeadlessCI: { 37 | base: "ChromeHeadless", 38 | flags: ["--no-sandbox"] 39 | } 40 | }, 41 | singleRun: true 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "./dist", 4 | "assets": [{ "input": "src/lib", "glob": "**/*.scss", "output": "" }], 5 | "lib": { 6 | "entryFile": "src/public_api.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/node_modules/.yarn-integrity: -------------------------------------------------------------------------------- 1 | { 2 | "systemParams": "darwin-arm64-127", 3 | "modulesFolders": [], 4 | "flags": [], 5 | "linkedModules": [], 6 | "topLevelPatterns": [], 7 | "lockfileEntries": {}, 8 | "files": [], 9 | "artifacts": {} 10 | } -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngx-datetime-range-picker", 3 | "version": "5.0.1", 4 | "homepage": "https://github.com/BhavinPatel04/ngx-datetime-range-picker", 5 | "bugs": "https://github.com/BhavinPatel04/ngx-datetime-range-picker/issues", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/BhavinPatel04/ngx-datetime-range-picker" 9 | }, 10 | "author": { 11 | "name": "Bhavin Patel", 12 | "url": "https://github.com/BhavinPatel04" 13 | }, 14 | "license": "MIT", 15 | "keywords": [ 16 | "angular", 17 | "angular2", 18 | "angular4", 19 | "angular5", 20 | "angular6", 21 | "angular7", 22 | "angular8", 23 | "angular9", 24 | "angular10", 25 | "ng2", 26 | "ng4", 27 | "ng5", 28 | "ng7", 29 | "ng8", 30 | "ng9", 31 | "ng10", 32 | "ngx", 33 | "boilerplate", 34 | "barebone", 35 | "datetime-range-picker", 36 | "datetime-range", 37 | "datetime", 38 | "date-time-range-picker", 39 | "date-time-range", 40 | "date-time", 41 | "date-range-picker", 42 | "date-range", 43 | "date", 44 | "time-range-picker", 45 | "time-range", 46 | "time", 47 | "ngx-datetime-range-picker", 48 | "ngx-datetime-range", 49 | "ngx-datetime", 50 | "ngx-date-time-range-picker", 51 | "ngx-date-time-range", 52 | "ngx-date-time", 53 | "ngx-date-range-picker", 54 | "ngx-date-range", 55 | "ngx-date", 56 | "ngx-time-range-picker", 57 | "ngx-time-range", 58 | "ngx-time" 59 | ], 60 | "private": false, 61 | "peerDependencies": { 62 | "@angular/cdk": "~19.0.6", 63 | "@angular/common": "~19.0.6", 64 | "@angular/core": "~19.0.6", 65 | "@angular/material": "~19.0.6", 66 | "moment": "^2.23.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | const tuple = (...args: T) => args; 2 | export const CalendarTypes = tuple("daily", "weekly", "monthly", "quarterly", "yearly"); 3 | export type CalendarType = typeof CalendarTypes[number]; 4 | 5 | export interface AriaLabelsOptions { 6 | inputField?: string; 7 | } 8 | 9 | export interface DateSide { 10 | label: string; 11 | months: string[]; 12 | years: string[]; 13 | itemRows: DateRow[]; 14 | } 15 | 16 | export interface TimeSide { 17 | hour: any[]; 18 | minute: any[]; 19 | meridian: any[]; 20 | } 21 | 22 | export interface DateCharacteristics { 23 | available?: boolean; 24 | inRange?: boolean; 25 | active?: boolean; 26 | today?: boolean; 27 | date?: string; 28 | } 29 | 30 | export interface ActiveItemSide extends DateCharacteristics { 31 | rowItemText?: string; 32 | firstDay?: string; 33 | lastDay?: string; 34 | formattedDateString?: string; 35 | } 36 | 37 | export interface CalendarSides { 38 | left?: DateSide | TimeSide | ActiveItemSide | string | boolean; 39 | right?: DateSide | TimeSide | ActiveItemSide | string | boolean; 40 | } 41 | 42 | export interface DateTimeRangeChangeOutput { 43 | activeRange: string; 44 | startDate: string | number; 45 | endDate: string | number; 46 | startTime?: string; 47 | endTime?: string; 48 | } 49 | 50 | export type DateTimeRangeModelChangeOutput = { [key in CalendarType]?: DateTimeRangeChangeOutput }; 51 | 52 | export interface Options { 53 | dateArray?: any[]; 54 | startDate?: string | number; 55 | endDate?: string | number; 56 | minDate?: string | number; 57 | maxDate?: string | number; 58 | startTime?: string; 59 | endTime?: string; 60 | minTime?: string; 61 | maxTime?: string; 62 | } 63 | 64 | export interface State { 65 | activeEndDate: string; 66 | activeItem: CalendarSides; 67 | activeRange: string; 68 | activeStartDate: string; 69 | calendarAvailable: CalendarSides; 70 | customRange: boolean; 71 | dates: CalendarSides; 72 | dateTitleText: CalendarSides; 73 | frequencyColumnHeader: string; 74 | isCalendarVisible: boolean; 75 | isValidFilter: boolean; 76 | isUserModelChange: boolean; 77 | localTimezone: string; 78 | selectedDateText: string; 79 | selectedHour: CalendarSides; 80 | selectedMeridian: CalendarSides; 81 | selectedMinute: CalendarSides; 82 | selectedMonth: CalendarSides; 83 | selectedTimezone: string; 84 | selectedYear: CalendarSides; 85 | sides: string[]; 86 | timeItems: string[]; 87 | times: CalendarSides; 88 | timeZones: string[]; 89 | todayTime: string; 90 | weekDayOptions: string[]; 91 | } 92 | 93 | export interface Settings { 94 | type?: string; 95 | modelKeys?: string[]; 96 | useLocalTimezone?: boolean; 97 | showTimezoneSelect?: boolean; 98 | timePicker?: boolean; 99 | timezoneSupport?: boolean; 100 | defaultTimezone?: string; 101 | inputClass?: string; 102 | inputDateFormat?: string; 103 | viewDateFormat?: string; 104 | outputDateFormat?: string; 105 | singleDatePicker?: boolean; 106 | componentDisabled?: boolean; 107 | label?: string; 108 | placeholder?: string; 109 | showRowNumber?: boolean; 110 | availableRanges?: Record; 111 | showRanges?: boolean; 112 | disableWeekends?: boolean; 113 | disableWeekdays?: boolean; 114 | retailCalendar?: boolean; 115 | displayBeginDate?: boolean; 116 | displayEndDate?: boolean; 117 | ariaLabels?: AriaLabelsOptions; 118 | } 119 | 120 | export interface Config extends Options, Settings { 121 | selectedTimezone?: string; 122 | } 123 | 124 | export interface DateRow { 125 | rowNumber: string; 126 | rowNumberText: string; 127 | items: ActiveItemSide[]; 128 | } 129 | 130 | export interface RowVariables { 131 | rowNumber: string; 132 | columns: number; 133 | } 134 | 135 | export interface RowItemVariables { 136 | itemCount: number; 137 | currentItemDate: string; 138 | rowItemText: string; 139 | firstDay: string; 140 | lastDay: string; 141 | } 142 | 143 | export interface RowOptions { 144 | type: string; 145 | monthStartWeekNumber: number; 146 | dateRows: number; 147 | year: string; 148 | itemCount: number; 149 | } 150 | 151 | export interface RowItemOptions { 152 | type: string; 153 | monthStartWeekNumber: number; 154 | dateRows: number; 155 | rowNumber: string; 156 | yearStartDate: string; 157 | year: number; 158 | rowItem: number; 159 | columns: number; 160 | } 161 | 162 | export type DateRangeModel = { [key in CalendarType]?: Options }; 163 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { MatButtonModule } from "@angular/material/button"; 3 | import { MatFormFieldModule } from "@angular/material/form-field"; 4 | import { MatIconModule } from "@angular/material/icon"; 5 | import { MatInputModule } from "@angular/material/input"; 6 | import { MatSelectModule } from "@angular/material/select"; 7 | 8 | const modules = [MatFormFieldModule, MatButtonModule, MatInputModule, MatIconModule, MatSelectModule]; 9 | @NgModule({ 10 | imports: modules, 11 | exports: modules 12 | }) 13 | export class MaterialModule {} 14 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | {{ config.label }} 14 | 15 | ({{ state.selectedTimezone }}) 16 | 17 | 18 | 41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 |
58 | {{ tz }} 59 |
60 |
61 |
62 | Today: {{ state.todayTime }} 63 |
64 |
65 |
    66 |
  • 67 |
    68 | 69 | 70 | 71 |
    72 |
    73 |
    74 | 75 |
    76 |
    81 |
    82 | 101 |
    102 |
    103 |
    104 | 105 | 111 | 116 | {{ month }} 117 | 118 | 119 | 120 |
    121 |
    122 | 123 | 129 | 134 | {{ year }} 135 | 136 | 137 | 138 |
    139 |
    140 |
    141 | 160 |
    161 | 162 | 163 | 164 | 165 | 168 | 169 | 170 | 171 | 172 | 175 | 213 | 214 | 215 |
    166 | {{ day }} 167 |
    173 | {{ row.rowNumberText }} 174 | 199 | 212 |
    216 |
    217 |
    218 |
    219 |
    220 |
    221 | 222 | 225 | 226 |
    227 |
    228 | 229 | 235 | 236 | {{ item }} 237 | 238 | 239 | 240 |
    241 |
    242 |
    243 |
  • 244 |
245 |
246 |
247 | 256 |
263 | 266 | 269 |
270 |
271 |
272 |
273 |
274 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.component.scss: -------------------------------------------------------------------------------- 1 | @use '@angular/material' as mat; 2 | @use 'sass:color'; 3 | 4 | $ndtrp-primary: ( 5 | 50: #e6f0fa, 6 | 100: #c1d8f3, 7 | 200: #97bfec, 8 | 300: #6da5e4, 9 | 400: #4e91de, 10 | 500: #2f7ed8, 11 | 600: #2a76d4, 12 | 700: #236bce, 13 | 800: #1d61c8, 14 | 900: #124ebf, 15 | A100: #eef3ff, 16 | A200: #bbd0ff, 17 | A400: #88adff, 18 | A700: #6e9bff, 19 | contrast: ( 20 | 50: #000000, 21 | 100: #000000, 22 | 200: #000000, 23 | 300: #000000, 24 | 400: #000000, 25 | 500: #ffffff, 26 | 600: #ffffff, 27 | 700: #ffffff, 28 | 800: #ffffff, 29 | 900: #ffffff, 30 | A100: #000000, 31 | A200: #000000, 32 | A400: #000000, 33 | A700: #000000 34 | ) 35 | ); 36 | 37 | // Plus imports for other components in your app. 38 | 39 | // Define a custom typography config that overrides the font-family as well as the 40 | // `headlines` and `body-1` levels. 41 | // $ndtrp-app-typography: mat.define-typography-config( 42 | // $font-family: "HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica, Arial,Lucida Grande,sans-serif" 43 | // ); 44 | 45 | // Include the common styles for Angular Material. We include this here so that you only 46 | // have to load a single css file for Angular Material in your app. 47 | // Be sure that you only ever include this mixin once! 48 | // @include mat-core($ndtrp-app-typography); 49 | 50 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 51 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 52 | // hue. Available color palettes: https://material.io/design/color/ 53 | $ndtrp-app-primary: mat.m2-define-palette($ndtrp-primary); //colors most widely used across all screens and components 54 | $ndtrp-app-accent: mat.m2-define-palette( 55 | $ndtrp-primary, 56 | A200, 57 | A100, 58 | A400 59 | ); //colors used for the floating action button and interactive elements 60 | 61 | // The warn palette is optional (defaults to red). 62 | $ndtrp-app-warn: mat.m2-define-palette(mat.$m2-red-palette); //colors used to convey error state 63 | 64 | // Create the theme object (a Sass map containing all of the palettes). 65 | $ndtrp-app-theme: mat.m2-define-light-theme( 66 | ( 67 | color: ( 68 | primary: $ndtrp-app-primary, 69 | accent: $ndtrp-app-accent, 70 | warn: $ndtrp-app-warn 71 | ) 72 | ) 73 | ); 74 | 75 | @mixin ngx-datetime-range-picker-component-theme($theme) { 76 | $ndtrp-primary-color: mat.get-theme-color($theme, primary); 77 | $ndtrp-typography-config: mat.m2-get-typography-config($theme, primary); 78 | // $ndtrp-primary-color: mat-color($ndtrp-primary-color-map); 79 | $ndtrp-primary-hover-color: color.adjust($ndtrp-primary-color, $blackness: 25%) !default; 80 | $ndtrp-primary-text-color: #333 !default; 81 | $ndtrp-secondary-text-color: color.adjust($ndtrp-primary-text-color, $lightness: 50%) !default; 82 | $ndtrp-icon-color: color.adjust($ndtrp-primary-text-color, $lightness: 40%) !default; 83 | $ndtrp-disabled-color: color.adjust($ndtrp-primary-text-color, $lightness: 70%) !default; 84 | $ndtrp-weekdays-color: #ffc266 !default; 85 | $ndtrp-range-button-background-color: color.adjust($ndtrp-primary-text-color, $lightness: 75%) !default; 86 | $ndtrp-range-button-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.16), 0 0 0 1px rgba(0, 0, 0, 0.08) !default; 87 | $ndtrp-font-family: mat.m2-font-family($ndtrp-typography-config); 88 | $ndtrp-font-size: mat.m2-font-size($ndtrp-typography-config, body-1); 89 | $ndtrp-border-color: #74777f !default; 90 | // @include mat.form-field-colors($theme); 91 | // @include mat.icon-colors($theme); 92 | // @include mat.button-colors($theme); 93 | // @include mat.select-colors($theme); 94 | 95 | .ngx-datetime-range-picker { 96 | position: relative; 97 | border-radius: 0; 98 | width: 100% !important; 99 | font-size: $ndtrp-font-size; 100 | font-family: $ndtrp-font-family; 101 | 102 | .full-width { 103 | width: 100%; 104 | } 105 | 106 | .date-input-icon-container { 107 | margin-left: 8px; 108 | } 109 | 110 | .fa { 111 | font-size: 100%; 112 | color: $ndtrp-icon-color; 113 | display: flex; 114 | align-items: center; 115 | justify-content: center; 116 | } 117 | 118 | .fa-calendar { 119 | color: $ndtrp-icon-color; 120 | } 121 | 122 | .retail-dateSelect { 123 | padding: 0 0 0 30px !important; 124 | } 125 | 126 | .retail-calendar:before { 127 | content: "\F073"; 128 | } 129 | 130 | .timezone-info { 131 | font-size: 95%; 132 | opacity: 0.8; 133 | font-weight: bolder; 134 | } 135 | 136 | .calendar-loading { 137 | min-width: 240px; 138 | min-height: 240px; 139 | padding: 54% 48% 46%; 140 | } 141 | 142 | .calendar-view { 143 | position: absolute; 144 | z-index: 1; 145 | width: fit-content; 146 | background-color: #fff; 147 | padding: 8px; 148 | margin-top: 2px; 149 | border-radius: 8px; 150 | box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 151 | 0px 1px 10px 0px rgba(0, 0, 0, 0.12); 152 | 153 | .date-select { 154 | .date-view { 155 | margin: 0; 156 | } 157 | } 158 | } 159 | 160 | .calendar-view:before, 161 | .calendar-view:after { 162 | position: absolute; 163 | display: inline-block; 164 | content: ""; 165 | } 166 | 167 | .timezone-select { 168 | font-size: 90%; 169 | display: flex; 170 | 171 | .timezones, 172 | .currentTime { 173 | display: inline-block; 174 | } 175 | 176 | .timezones { 177 | margin-right: auto; 178 | white-space: nowrap; 179 | left: 8px; 180 | } 181 | 182 | .currentTime { 183 | margin-left: auto; 184 | white-space: nowrap; 185 | right: 8px; 186 | } 187 | 188 | .border-separator { 189 | border-right: 2px solid #ccc; 190 | padding-right: 8px; 191 | margin-right: 8px; 192 | } 193 | 194 | .timezone { 195 | display: inline-block; 196 | cursor: pointer; 197 | } 198 | } 199 | 200 | .active-timezone.code { 201 | font-weight: bolder; 202 | } 203 | 204 | .dateTitleInput, 205 | .date-dropdown-container, 206 | .time-item-container { 207 | .mdc-text-field--filled:not(.mdc-text-field--disabled), 208 | .mat-mdc-form-field-focus-overlay { 209 | background-color: transparent; 210 | } 211 | 212 | .mdc-text-field--outlined .mat-mdc-form-field-infix, 213 | .mdc-text-field--no-label .mat-mdc-form-field-infix { 214 | padding: 0; 215 | min-height: 36px; 216 | display: flex; 217 | align-items: center; 218 | } 219 | } 220 | 221 | .date-dropdown-container, 222 | .time-item-container { 223 | display: flex; 224 | justify-content: center; 225 | width: 100%; 226 | 227 | .mat-mdc-form-field { 228 | width: 100%; 229 | } 230 | 231 | .mat-mdc-text-field-wrapper { 232 | padding: 0; 233 | 234 | .mat-mdc-form-field-infix { 235 | border-top: 0; 236 | width: auto; 237 | 238 | .mat-mdc-select-arrow-wrapper { 239 | width: 24px; 240 | justify-content: center; 241 | } 242 | } 243 | 244 | .mdc-line-ripple::before, 245 | .mdc-line-ripple::after { 246 | border-bottom-width: 0px; 247 | } 248 | } 249 | 250 | .date-dropdown { 251 | margin: 0 4px; 252 | width: 50%; 253 | } 254 | } 255 | 256 | .date-select { 257 | display: inline-flex; 258 | gap: 4px; 259 | 260 | .date-pick-container { 261 | position: relative; 262 | display: flex; 263 | flex-direction: column; 264 | gap: 8px; 265 | } 266 | } 267 | 268 | .ranges { 269 | min-width: 160px; 270 | padding: 0 2px; 271 | position: relative; 272 | display: flex; 273 | flex-direction: column; 274 | gap: 4px; 275 | 276 | .calendar-range { 277 | width: 100%; 278 | white-space: nowrap; 279 | border-radius: 9999px; 280 | color: $ndtrp-primary-color; 281 | line-height: 30px; 282 | cursor: pointer; 283 | border: 1px solid $ndtrp-border-color; 284 | } 285 | 286 | .active-range { 287 | background-color: $ndtrp-primary-color; 288 | border: 1px solid $ndtrp-primary-color; 289 | color: #fff; 290 | } 291 | 292 | .active-range:hover { 293 | color: #fff; 294 | background-color: $ndtrp-primary-hover-color; 295 | border-color: $ndtrp-primary-hover-color; 296 | } 297 | 298 | .range-select-button-container { 299 | white-space: nowrap; 300 | width: 96%; 301 | } 302 | 303 | .range-select-button { 304 | width: 50%; 305 | border-radius: 0px; 306 | line-height: 30px; 307 | border-radius: 9999px; 308 | } 309 | 310 | .range-select-button:first-child { 311 | margin-right: 3px; 312 | } 313 | 314 | .range-select-button:last-child { 315 | margin-left: 3px; 316 | } 317 | 318 | .range-select-apply-button { 319 | color: #fff; 320 | background-color: $ndtrp-primary-color; 321 | border: 1px solid $ndtrp-primary-color; 322 | } 323 | 324 | .range-select-cancel-button { 325 | color: $ndtrp-primary-color; 326 | border: 1px solid $ndtrp-border-color; 327 | } 328 | 329 | .range-select-button-bottom { 330 | position: absolute; 331 | bottom: 4px; 332 | } 333 | } 334 | 335 | .calendar-container { 336 | display: flex; 337 | padding: 0; 338 | gap: 8px; 339 | margin: 0; 340 | } 341 | 342 | .calendar { 343 | float: left; 344 | width: auto; 345 | display: flex; 346 | flex-direction: column; 347 | } 348 | 349 | .calendar-label { 350 | text-align: center; 351 | position: relative; 352 | width: 100%; 353 | } 354 | 355 | .prev, 356 | .next { 357 | .available, 358 | .disabled { 359 | display: flex; 360 | justify-content: center; 361 | } 362 | height: 100%; 363 | } 364 | 365 | .calendar-week-day { 366 | color: $ndtrp-weekdays-color; 367 | font-weight: normal; 368 | font-size: 95%; 369 | } 370 | 371 | .capitalize { 372 | text-transform: capitalize; 373 | } 374 | 375 | .dateTitleInput { 376 | position: relative; 377 | border: none; 378 | width: 100%; 379 | float: left; 380 | display: block; 381 | margin-bottom: 4px; 382 | 383 | .mat-mdc-form-field { 384 | min-height: 36px; 385 | } 386 | 387 | .dateSelect { 388 | text-align: center; 389 | } 390 | 391 | .dateSelect:hover { 392 | pointer-events: none; 393 | background: transparent; 394 | cursor: default; 395 | } 396 | 397 | i { 398 | position: absolute !important; 399 | top: 11px !important; 400 | left: 10px !important; 401 | color: $ndtrp-icon-color !important; 402 | } 403 | 404 | .mat-mdc-text-field-wrapper { 405 | .mdc-line-ripple::before, 406 | .mdc-line-ripple::after { 407 | border-bottom-width: 1px; 408 | } 409 | } 410 | } 411 | 412 | .calendar-table { 413 | flex-grow: 1; 414 | 415 | .calendar-side-container { 416 | .calendar-label-container { 417 | display: flex; 418 | align-items: center; 419 | width: 100%; 420 | align-items: center; 421 | min-height: 36px; 422 | } 423 | 424 | table { 425 | table-layout: fixed; 426 | min-width: 100%; 427 | border-collapse: collapse; 428 | 429 | th, 430 | td { 431 | white-space: nowrap; 432 | text-align: center; 433 | } 434 | 435 | td { 436 | button { 437 | border: none; 438 | background: transparent; 439 | cursor: pointer; 440 | padding: 8px; 441 | width: 100%; 442 | color: inherit; 443 | 444 | &:disabled { 445 | cursor: default; 446 | color: $ndtrp-secondary-text-color; 447 | } 448 | } 449 | } 450 | 451 | td.rowNumber { 452 | color: color.adjust($ndtrp-primary-text-color, $lightness: 40%); 453 | border-right: 1px solid color.adjust($ndtrp-primary-text-color, $lightness: 60%); 454 | font-size: 70%; 455 | padding: 8px; 456 | } 457 | 458 | td.rowNumberPlaceholder { 459 | padding: 0 !important; 460 | min-width: 0px !important; 461 | } 462 | 463 | td.valid { 464 | cursor: pointer; 465 | } 466 | 467 | td.available:hover, 468 | th.available:hover { 469 | background-color: color.adjust($ndtrp-primary-text-color, $lightness: 70%); 470 | border-radius: 8px; 471 | } 472 | 473 | td.in-range { 474 | background-color: color.adjust($ndtrp-primary-text-color, $lightness: 76%); 475 | color: $ndtrp-primary-color; 476 | } 477 | 478 | :nth-child(1 of .in-range) { 479 | &.active { 480 | &.singleDatePicker { 481 | border-radius: 8px; 482 | } 483 | 484 | &.left:not(.singleDatePicker) { 485 | border-radius: 8px 0 0 8px; 486 | } 487 | 488 | &.right:not(.singleDatePicker) { 489 | border-radius: 0 8px 8px 0; 490 | } 491 | } 492 | } 493 | 494 | :nth-last-child(1 of .in-range) { 495 | &.active { 496 | &.singleDatePicker { 497 | border-radius: 8px; 498 | } 499 | 500 | &.left:not(.singleDatePicker) { 501 | border-radius: 8px 0 0 8px; 502 | } 503 | 504 | &.right:not(.singleDatePicker) { 505 | border-radius: 0 8px 8px 0; 506 | } 507 | } 508 | } 509 | 510 | td.today { 511 | &.active { 512 | div { 513 | border-bottom: 2px solid #fff; 514 | } 515 | } 516 | 517 | &:not(.active) { 518 | div { 519 | border-bottom: 2px solid $ndtrp-primary-color; 520 | } 521 | } 522 | } 523 | } 524 | } 525 | } 526 | 527 | td.active, 528 | .fa.active { 529 | background-color: $ndtrp-primary-color !important; 530 | color: #fff !important; 531 | } 532 | 533 | .disabled { 534 | color: $ndtrp-disabled-color; 535 | pointer-events: none; 536 | } 537 | 538 | .month-select, 539 | .year-select, 540 | .timeItem-select { 541 | background: #fff; 542 | padding: 0; 543 | box-shadow: none; 544 | text-align: center; 545 | } 546 | 547 | .dropdown-menu { 548 | position: absolute; 549 | min-width: unset !important; 550 | font-size: 95%; 551 | border-radius: 0px; 552 | max-height: 100px; 553 | overflow: auto; 554 | } 555 | 556 | .hide { 557 | display: none; 558 | } 559 | 560 | .show { 561 | display: block; 562 | } 563 | 564 | .time-picker-container { 565 | display: flex; 566 | padding: 0; 567 | gap: 8px; 568 | 569 | .time-select { 570 | position: relative; 571 | padding: 0 4px; 572 | text-align: center; 573 | width: 50%; 574 | margin: 4px 0; 575 | border: 1px solid $ndtrp-border-color; 576 | border-radius: 8px; 577 | display: flex; 578 | align-items: center; 579 | width: 100%; 580 | 581 | .clock-icon-container { 582 | display: flex; 583 | align-items: center; 584 | } 585 | 586 | .time-item-container { 587 | width: 50%; 588 | margin: 0 4px; 589 | } 590 | } 591 | } 592 | } 593 | } 594 | 595 | @mixin ngx-datetime-range-picker-theme($app-theme: $ndtrp-app-theme) { 596 | @include ngx-datetime-range-picker-component-theme($app-theme); 597 | 598 | //Setting the overlay very high so it always appears on top 599 | $cdk-z-index-overlay-container: 999999; 600 | @import "@angular/cdk/overlay-prebuilt.css"; 601 | 602 | //Custom 603 | .mat-form-field-placeholder { 604 | font-size: 115% !important; 605 | } 606 | 607 | .mat-form-field { 608 | .mat-form-field-wrapper { 609 | padding-bottom: 0; 610 | } 611 | 612 | .mat-form-field-underline { 613 | bottom: 0; 614 | } 615 | } 616 | } 617 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from "@angular/core/testing"; 2 | import { provideZoneChangeDetection, SimpleChanges } from "@angular/core"; 3 | import { NoopAnimationsModule } from "@angular/platform-browser/animations"; 4 | import { NgxDatetimeRangePickerModule } from "./ngx-datetime-range-picker.module"; 5 | import { NgxDatetimeRangePickerComponent } from "./ngx-datetime-range-picker.component"; 6 | import { DateSide, DateTimeRangeChangeOutput, DateTimeRangeModelChangeOutput } from "./interfaces"; 7 | 8 | declare let require: any; 9 | const moment = require("moment"); 10 | 11 | describe("NgxDatetimeRangePickerComponent", () => { 12 | let component: NgxDatetimeRangePickerComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(() => { 16 | TestBed.configureTestingModule({ 17 | imports: [NgxDatetimeRangePickerModule, NoopAnimationsModule], 18 | providers: [provideZoneChangeDetection({ ignoreChangesOutsideZone: true })], 19 | declarations: [] 20 | }).compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(NgxDatetimeRangePickerComponent); 25 | component = fixture.debugElement.componentInstance; 26 | 27 | component.settings = {}; 28 | component.options = {}; 29 | component.config.minDate = "2017-01-01"; 30 | component.config.maxDate = "2017-03-31"; 31 | component.config.startDate = "2017-02-01"; 32 | component.config.endDate = "2017-03-01"; 33 | }); 34 | 35 | it("#ngOnChnages", () => { 36 | spyOn(component, "initialize"); 37 | spyOn(component, "parseOptions"); 38 | spyOn(component, "updateInputField"); 39 | 40 | const changes: SimpleChanges = { 41 | options: { 42 | previousValue: [{ id: 0, name: "US" }], 43 | firstChange: true, 44 | isFirstChange: () => { 45 | return true; 46 | }, 47 | currentValue: [ 48 | { 49 | singleDatePicker: true, 50 | type: "weekly", 51 | startDate: "2017-02-01", 52 | endDate: "2017-03-01", 53 | viewDateFormat: "MMM DD, YYYY" 54 | } 55 | ] 56 | } 57 | }; 58 | component.ngOnChanges(changes); 59 | fixture.detectChanges(); 60 | expect(component.options).toBeDefined(); 61 | }); 62 | 63 | it("#onDateRangeInputChange", () => { 64 | spyOn(component, "dateRangeSelected"); 65 | component.onDateRangeInputChange(); 66 | expect(component.dateRangeSelected).toHaveBeenCalled(); 67 | }); 68 | 69 | it("#setDisabledState", () => { 70 | component.setDisabledState(false); 71 | expect(component.config.componentDisabled).toBeFalsy(); 72 | }); 73 | 74 | it("#onComponentClick", () => { 75 | component.state.isCalendarVisible = false; 76 | component.onComponentClick(); 77 | expect(component.state.isCalendarVisible).toBeTruthy(); 78 | }); 79 | 80 | it("#onFocusInput", () => { 81 | const event: MouseEvent = { 82 | target: { 83 | value: "text" 84 | } 85 | } as any; 86 | component.onFocusInput(event); 87 | }); 88 | 89 | it("#onBlurInput", () => { 90 | const event: MouseEvent = { 91 | target: { 92 | value: "text" 93 | } 94 | } as any; 95 | component.onBlurInput(event); 96 | expect(component.state.selectedDateText).toEqual("text"); 97 | }); 98 | 99 | it("#onCalendarClose", () => { 100 | component.config.startDate = "2017-01-01"; 101 | component.config.endDate = "2017-12-31"; 102 | component.onCalendarClose(); 103 | expect(component.state.isCalendarVisible).toBeFalsy(); 104 | }); 105 | 106 | it("#isPrevAvailable", () => { 107 | component.config.minDate = "2017-01-01"; 108 | const side = "left"; 109 | component.state.selectedMonth[side] = "Feb"; 110 | component.state.selectedYear[side] = "2017"; 111 | expect(component.isPrevAvailable(side)).toBeTruthy(); 112 | 113 | component.state.selectedMonth[side] = "Jan"; 114 | expect(component.isPrevAvailable(side)).toBeFalsy(); 115 | }); 116 | 117 | it("#isNextAvailable", () => { 118 | component.config.maxDate = "2017-03-31"; 119 | const side = "left"; 120 | component.state.selectedMonth[side] = "Feb"; 121 | component.state.selectedYear[side] = "2017"; 122 | expect(component.isNextAvailable(side)).toBeTruthy(); 123 | 124 | component.state.selectedMonth[side] = "Mar"; 125 | expect(component.isNextAvailable(side)).toBeFalsy(); 126 | }); 127 | 128 | it("#getCalendarColspan", () => { 129 | component.config.type = "daily"; 130 | expect(component.getCalendarColspan()).toEqual(6); 131 | }); 132 | 133 | it("#getCalendarRowItemColspan", () => { 134 | component.config.type = "monthly"; 135 | expect(component.getCalendarRowItemColspan()).toEqual(3); 136 | }); 137 | 138 | it("#onClickPrevious", () => { 139 | const side = "left"; 140 | component.state.selectedMonth[side] = "Feb"; 141 | component.state.selectedYear[side] = "2017"; 142 | component.onClickPrevious(side); 143 | expect(component.state.dates[side]).toBeDefined(); 144 | }); 145 | 146 | it("#onClickNext", () => { 147 | const side = "left"; 148 | component.state.selectedMonth[side] = "Feb"; 149 | component.state.selectedYear[side] = "2017"; 150 | component.onClickNext(side); 151 | expect(component.state.dates[side]).toBeDefined(); 152 | }); 153 | 154 | it("#onCellClick", () => { 155 | spyOn(component, "doApply"); 156 | let item = { 157 | date: "2017-02-04", 158 | available: false 159 | }; 160 | component.onCellClick(item, null, "left"); 161 | 162 | item = { 163 | date: "2017-02-02", 164 | available: true 165 | }; 166 | component.onCellClick(item, null, "left"); 167 | expect(component.config.startDate).toEqual(item.date); 168 | 169 | component.config.startDate = "2017-02-02"; 170 | item = { 171 | date: "2017-02-04", 172 | available: true 173 | }; 174 | component.onCellClick(item, null, "left"); 175 | expect(component.config.endDate).toEqual(item.date); 176 | 177 | component.config.singleDatePicker = true; 178 | component.onCellClick(item, null, "left"); 179 | expect(component.config.endDate).toEqual(component.config.startDate); 180 | }); 181 | 182 | it("#onCellMouseEnter", () => { 183 | let item = { 184 | date: "2017-02-04", 185 | available: false 186 | }; 187 | component.onCellMouseEnter(item, null, "left"); 188 | 189 | component.config.endDate = null; 190 | component.config.startDate = "2017-02-02"; 191 | item = { 192 | date: "2017-02-02", 193 | available: true 194 | }; 195 | component.state.dates = { 196 | left: { 197 | label: "Feb 2017", 198 | months: ["Jan", "Feb", "Mar"], 199 | years: ["2017"], 200 | itemRows: [ 201 | { 202 | rowNumber: "", 203 | rowNumberText: "", 204 | items: [ 205 | { 206 | date: "2017-02-02", 207 | rowItemText: "2", 208 | available: true, 209 | inRange: true, 210 | active: false, 211 | today: false 212 | } 213 | ] 214 | } 215 | ] 216 | } 217 | }; 218 | component.onCellMouseEnter(item, null, "left"); 219 | }); 220 | 221 | it("#onCellMouseLeave", () => { 222 | component.config.endDate = null; 223 | component.state.dates = { 224 | left: { 225 | label: "Feb 2017", 226 | months: ["Jan", "Feb", "Mar"], 227 | years: ["2017"], 228 | itemRows: [ 229 | { 230 | rowNumber: "", 231 | rowNumberText: "", 232 | items: [ 233 | { 234 | date: "2017-02-02", 235 | rowItemText: "2", 236 | available: true, 237 | inRange: true, 238 | active: false, 239 | today: false 240 | } 241 | ] 242 | } 243 | ] 244 | } 245 | }; 246 | component.onCellMouseLeave(); 247 | }); 248 | 249 | it("#onRangeClick", () => { 250 | spyOn(component, "doApply"); 251 | const startDate = moment("2017-02-02", "YYYY-MM-DD") 252 | .subtract(6, "days") 253 | .format("YYYY-MM-DD"); 254 | const endDate = "2017-02-02"; 255 | let rangeLabel = "Last 7 Days"; 256 | const dateRangeModel = { 257 | startDate, 258 | endDate 259 | }; 260 | component.onRangeClick(rangeLabel, dateRangeModel); 261 | expect(component.config.startDate).toEqual(startDate); 262 | expect(component.config.endDate).toEqual(endDate); 263 | 264 | rangeLabel = "Custom Range"; 265 | component.state.customRange = true; 266 | component.onRangeClick(rangeLabel, dateRangeModel); 267 | expect(component.state.sides.length).toEqual(0); 268 | }); 269 | 270 | it("#onCalendarLabelChange", () => { 271 | spyOn(component, "doApply"); 272 | const label = "Feb"; 273 | let side = "left"; 274 | let type = "month"; 275 | component.state.selectedYear[side] = "2017"; 276 | component.onCalendarLabelChange(label, side, type); 277 | expect(component.state.dates[side]).toBeDefined(); 278 | 279 | component.config.type = "yearly"; 280 | type = "year"; 281 | side = "right"; 282 | component.state.selectedYear[side] = "2017"; 283 | component.state.selectedYear.left = "2017"; 284 | component.onCalendarLabelChange(label, side, type); 285 | expect(component.doApply).toHaveBeenCalled(); 286 | }); 287 | 288 | it("#initialize", () => { 289 | component.initialize(); 290 | expect(component.state.sides.length).toEqual(0); 291 | expect(component.state.dates).toEqual({ 292 | left: {} as DateSide, 293 | right: {} as DateSide 294 | }); 295 | expect(component.state.activeStartDate).toBeNull(); 296 | expect(component.state.activeEndDate).toBeNull(); 297 | expect(component.state.frequencyColumnHeader).toBeNull(); 298 | expect(component.state.customRange).toBeFalsy(); 299 | expect(component.state.activeRange).toEqual(null); 300 | }); 301 | 302 | it("#parseOptions", () => { 303 | component.options = { 304 | startDate: "2017-02-01", 305 | endDate: "2017-03-01" 306 | }; 307 | component.settings = { 308 | singleDatePicker: true, 309 | type: "weekly", 310 | viewDateFormat: "MMM DD, YYYY" 311 | }; 312 | component.parseOptions(); 313 | expect(component.config.minDate).toBeDefined(); 314 | expect(component.config.maxDate).toBeDefined(); 315 | expect(component.config.startDate).toBeDefined(); 316 | expect(component.config.endDate).toBeDefined(); 317 | }); 318 | 319 | it("#parseOptionsToDefaultDateFormat", () => { 320 | component.parseOptionsToDefaultDateFormat(); 321 | expect(component.config.minDate).toBeDefined(); 322 | expect(component.config.maxDate).toBeDefined(); 323 | expect(component.config.startDate).toBeDefined(); 324 | expect(component.config.endDate).toBeDefined(); 325 | }); 326 | 327 | it("#handleDateArray", () => { 328 | component.config.dateArray = ["2017-02-02"]; 329 | component.handleDateArray(); 330 | expect(component.config.minDate).toBeDefined(); 331 | expect(component.config.maxDate).toBeDefined(); 332 | expect(component.config.startDate).toBeDefined(); 333 | expect(component.config.endDate).toBeDefined(); 334 | }); 335 | 336 | it("#sanitizeDates", () => { 337 | component.config.retailCalendar = true; 338 | component.config.type = "monthly"; 339 | component.sanitizeDates(); 340 | expect(component.config.minDate).toBeDefined(); 341 | expect(component.config.maxDate).toBeDefined(); 342 | expect(component.config.startDate).toBeDefined(); 343 | expect(component.config.endDate).toBeDefined(); 344 | }); 345 | 346 | it("#processRanges", () => { 347 | component.config.showRanges = true; 348 | component.config.availableRanges = {}; 349 | component.processRanges(); 350 | expect(component.config.availableRanges).toBeDefined(); 351 | 352 | component.state.customRange = false; 353 | component.config.showRanges = false; 354 | component.processRanges(); 355 | expect(component.state.customRange).toBeTruthy(); 356 | }); 357 | 358 | it("#selectActiveRange", () => { 359 | spyOn(component, "onRangeClick"); 360 | const startDate = moment("2017-02-02", "YYYY-MM-DD") 361 | .subtract(6, "days") 362 | .format("YYYY-MM-DD"); 363 | const endDate = "2017-02-02"; 364 | component.config.availableRanges = { 365 | "Last 7 Days": { 366 | startDate, 367 | endDate 368 | } 369 | }; 370 | component.state.activeRange = null; 371 | component.selectActiveRange(); 372 | expect(component.state.activeRange).toEqual("Custom Range"); 373 | 374 | component.config.startDate = startDate; 375 | component.config.endDate = endDate; 376 | component.selectActiveRange(); 377 | expect(component.state.activeRange).toEqual("Last 7 Days"); 378 | }); 379 | 380 | it("#generateCalendar", () => { 381 | component.config.type = "daily"; 382 | const date = component.config.startDate; 383 | const side = "left"; 384 | component.generateCalendar(date, side); 385 | expect(component.state.calendarAvailable[side]).toBeTruthy(); 386 | }); 387 | 388 | it("#updateInputField", () => { 389 | const endDate = component.config.endDate; 390 | component.config.type = "weekly"; 391 | component.config.retailCalendar = false; 392 | component.state.activeItem = { 393 | left: { 394 | firstDay: moment("2017-10-06", "YYYY-MM-DD"), 395 | lastDay: moment("2017-10-06", "YYYY-MM-DD"), 396 | rowItemText: "Test" 397 | }, 398 | right: { 399 | firstDay: moment("2017-10-06", "YYYY-MM-DD"), 400 | lastDay: moment("2017-10-06", "YYYY-MM-DD"), 401 | rowItemText: "Test" 402 | } 403 | }; 404 | component.config.viewDateFormat = "YYYY-MM-DD"; 405 | 406 | component.updateInputField(); 407 | expect(component.state.selectedDateText).toEqual(`2017-01-29 - 2017-03-01`); 408 | expect(component.state.dateTitleText.left).toEqual("Test (2017-10-06 - 2017-10-06)"); 409 | expect(component.state.dateTitleText.right).toEqual("Test (2017-10-06 - 2017-10-06)"); 410 | 411 | component.config.singleDatePicker = true; 412 | component.updateInputField(); 413 | expect(component.state.selectedDateText).toEqual(`2017-01-29 - 2017-03-01`); 414 | expect(component.state.dateTitleText.left).toEqual("Test (2017-10-06 - 2017-10-06)"); 415 | expect(component.state.dateTitleText.right).toEqual("Test (2017-10-06 - 2017-10-06)"); 416 | 417 | component.config.displayBeginDate = true; 418 | component.updateInputField(); 419 | expect(component.state.selectedDateText).toEqual(`2017-01-29`); 420 | expect(component.state.dateTitleText.left).toEqual("Test (2017-10-06 - 2017-10-06)"); 421 | expect(component.state.dateTitleText.right).toEqual("Test (2017-10-06 - 2017-10-06)"); 422 | 423 | component.config.displayBeginDate = false; 424 | component.config.displayEndDate = true; 425 | component.updateInputField(); 426 | expect(component.state.selectedDateText).toEqual(`${endDate}`); 427 | expect(component.state.dateTitleText.left).toEqual("Test (2017-10-06 - 2017-10-06)"); 428 | expect(component.state.dateTitleText.right).toEqual("Test (2017-10-06 - 2017-10-06)"); 429 | }); 430 | 431 | it("#updateActiveItemInputField", () => { 432 | component.config.viewDateFormat = "YYYY-MM-DD"; 433 | component.state.activeItem = { 434 | left: { 435 | firstDay: moment("2017-10-06", "YYYY-MM-DD"), 436 | lastDay: moment("2017-10-06", "YYYY-MM-DD"), 437 | rowItemText: "Test" 438 | }, 439 | right: { 440 | firstDay: moment("2017-10-06", "YYYY-MM-DD"), 441 | lastDay: moment("2017-10-06", "YYYY-MM-DD"), 442 | rowItemText: "Test" 443 | } 444 | }; 445 | 446 | component.updateActiveItemInputField(); 447 | expect(component.state.selectedDateText).toEqual(``); 448 | expect(component.state.dateTitleText.left).toEqual("2017-10-06"); 449 | expect(component.state.dateTitleText.right).toEqual("2017-10-06"); 450 | }); 451 | 452 | it("#dateRangeSelected", () => { 453 | spyOn(component, "getDateRangeModel"); 454 | component.dateRangeSelected(); 455 | expect(component.state.isCalendarVisible).toBeFalsy(); 456 | }); 457 | 458 | it("#getDateRangeModel", () => { 459 | const startDate = component.config.startDate; 460 | const endDate = component.config.endDate; 461 | component.state.activeRange = "activeRange"; 462 | component.config.type = "daily"; 463 | component.config.outputDateFormat = "YYYY-MM-DD"; 464 | expect(component.getDateRangeModel()).toEqual({ 465 | daily: { 466 | activeRange: component.state.activeRange, 467 | startDate: startDate as string, 468 | endDate: endDate as string 469 | } as DateTimeRangeChangeOutput 470 | } as DateTimeRangeModelChangeOutput); 471 | }); 472 | 473 | it("#doApply", () => { 474 | spyOn(component, "dateRangeSelected"); 475 | spyOn(component, "updateInputField"); 476 | const startDate = component.config.startDate; 477 | const endDate = component.config.endDate; 478 | component.doApply(); 479 | expect(component.state.activeStartDate).toEqual(startDate as string); 480 | expect(component.state.activeEndDate).toEqual(endDate as string); 481 | }); 482 | }); 483 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.constants.ts: -------------------------------------------------------------------------------- 1 | import { ActiveItemSide, DateSide, AriaLabelsOptions, Options, Settings, State } from "./interfaces"; 2 | 3 | declare let require: any; 4 | const moment = require("moment"); 5 | const USA_MST_TZ_CODE = "MST"; 6 | const USA_TZ_CODE = "PST"; 7 | const EU_TZ_CODE = "CET"; 8 | 9 | function getLocalTimezone(): string { 10 | const tz: string = /\((.*)\)/.exec(new Date().toString())[1]; 11 | 12 | if (tz === "Central Europe Standard Time") { 13 | return EU_TZ_CODE; 14 | } else { 15 | return USA_MST_TZ_CODE; 16 | } 17 | } 18 | 19 | export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD"; 20 | 21 | export const NgxDatetimeRangePickerConstants = { 22 | DEFAULT: { 23 | OPTIONS: { 24 | dateArray: [], 25 | startDate: moment().format("YYYY-MM-DD") as string, 26 | endDate: moment().format("YYYY-MM-DD") as string, 27 | minDate: moment() 28 | .subtract(2, "year") 29 | .startOf("year") 30 | .format("YYYY-MM-DD") as string, 31 | maxDate: moment().format("YYYY-MM-DD") as string, 32 | startTime: "00:00", 33 | endTime: "23:59" 34 | }, 35 | SETTINGS: { 36 | type: "daily", 37 | modelKeys: ["daily", "weekly", "monthly", "quarterly", "yearly"], 38 | showTimezoneSelect: false, 39 | useLocalTimezone: false, 40 | timePicker: false, 41 | inputClass: "m1drp", 42 | inputDateFormat: null, 43 | viewDateFormat: DEFAULT_DATE_FORMAT, 44 | outputDateFormat: DEFAULT_DATE_FORMAT, 45 | singleDatePicker: false, 46 | componentDisabled: false, 47 | placeholder: "Select Date", 48 | showRowNumber: false, 49 | availableRanges: {}, 50 | showRanges: true, 51 | disableWeekends: false, 52 | disableWeekdays: false, 53 | retailCalendar: false, 54 | displayBeginDate: false, 55 | displayEndDate: false, 56 | ariaLabels: { 57 | inputField: "Date Range Input Field" 58 | } as AriaLabelsOptions 59 | }, 60 | STATE: { 61 | activeEndDate: null, 62 | activeItem: { 63 | left: {} as ActiveItemSide, 64 | right: {} as ActiveItemSide 65 | }, 66 | activeRange: null, 67 | activeStartDate: null, 68 | calendarAvailable: { 69 | left: false, 70 | right: false 71 | }, 72 | customRange: false, 73 | dates: { 74 | left: {} as DateSide, 75 | right: {} as DateSide 76 | }, 77 | dateTitleText: { 78 | left: "", 79 | right: "" 80 | }, 81 | frequencyColumnHeader: null, 82 | isCalendarVisible: false, 83 | isValidFilter: false, 84 | isUserModelChange: true, 85 | localTimezone: getLocalTimezone(), 86 | selectedDateText: "", 87 | selectedHour: { 88 | left: "", 89 | right: "" 90 | }, 91 | selectedMeridian: { 92 | left: "", 93 | right: "" 94 | }, 95 | selectedMinute: { 96 | left: "", 97 | right: "" 98 | }, 99 | selectedMonth: { 100 | left: "", 101 | right: "" 102 | }, 103 | selectedTimezone: undefined, // Since "useLocalTimezone: false" by default; 104 | selectedYear: { 105 | left: "", 106 | right: "" 107 | }, 108 | sides: [], 109 | timeItems: ["hour", "minute"], 110 | times: { 111 | left: "", 112 | right: "" 113 | }, 114 | timeZones: [USA_TZ_CODE, EU_TZ_CODE], 115 | todayTime: "", 116 | weekDayOptions: ["su", "mo", "tu", "we", "th", "fr", "sa"] 117 | }, 118 | TIME_FORMAT: "HH:mm", 119 | RANGES: { 120 | daily: [ 121 | { label: "Last 7 Days", count: 6 }, 122 | { label: "Last 30 Days", count: 29 }, 123 | { label: "Last 90 Days", count: 89 } 124 | ], 125 | weekly: [ 126 | { label: "Last 4 Weeks", count: 3 }, 127 | { label: "Last 13 Weeks", count: 12 }, 128 | { label: "Last 26 Weeks", count: 25 } 129 | ], 130 | monthly: [ 131 | { label: "Last 3 Months", count: 2 }, 132 | { label: "Last 6 Months", count: 5 }, 133 | { label: "Last 9 Months", count: 8 } 134 | ], 135 | quarterly: [ 136 | { label: "Last 2 Quarters", count: 1 }, 137 | { label: "Last 4 Quarters", count: 3 } 138 | ], 139 | yearly: [{ label: "Last Year", count: 1 }] 140 | } 141 | }, 142 | CONSTANT: { 143 | MONTHS_AVAILABLE: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], 144 | TIMES_AVAILABLE: ["hour", "minute"], 145 | MOMENT_CONVERSION_MAP: { 146 | daily: "day", 147 | weekly: "week", 148 | monthly: "month", 149 | quarterly: "quarter", 150 | yearly: "year" 151 | }, 152 | USA_MST_TZ_CODE, 153 | USA_TZ_CODE, 154 | EU_TZ_CODE, 155 | OFFSETS: { 156 | [USA_TZ_CODE]: { 157 | SO: -7, 158 | WO: -8 159 | }, 160 | [EU_TZ_CODE]: { 161 | SO: 1, 162 | WO: 0 163 | } 164 | }, 165 | TZ_NAMES: { 166 | [USA_MST_TZ_CODE]: "America/Phoenix", 167 | [USA_TZ_CODE]: "America/Los_Angeles", 168 | [EU_TZ_CODE]: "Europe/Berlin" 169 | } 170 | } 171 | }; 172 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders, Optional, SkipSelf } from "@angular/core"; 2 | import { CommonModule } from "@angular/common"; 3 | import { FormsModule } from "@angular/forms"; 4 | import { MaterialModule } from "./material/material.module"; 5 | import { provideHttpClient, withInterceptorsFromDi } from "@angular/common/http"; 6 | import { NgxDatetimeRangePickerComponent } from "./ngx-datetime-range-picker.component"; 7 | import { ObjNgFor } from "./pipes/objNgFor.pipe"; 8 | import { NgxDatetimeRangePickerService } from "./ngx-datetime-range-picker.service"; 9 | 10 | @NgModule({ 11 | declarations: [ObjNgFor, NgxDatetimeRangePickerComponent], 12 | exports: [NgxDatetimeRangePickerComponent, MaterialModule], 13 | imports: [CommonModule, FormsModule, MaterialModule], 14 | providers: [provideHttpClient(withInterceptorsFromDi())] 15 | }) 16 | export class NgxDatetimeRangePickerModule { 17 | constructor(@Optional() @SkipSelf() parentModule: NgxDatetimeRangePickerModule) { 18 | if (parentModule) { 19 | throw new Error(`ERR_NGX_DATETIME_RANGE_PICKER: 20 | NgxDatetimeRangePickerModule is already loaded. Import it in the AppModule only`); 21 | } 22 | } 23 | 24 | public static forRoot(): ModuleWithProviders { 25 | return { 26 | ngModule: NgxDatetimeRangePickerModule, 27 | providers: [NgxDatetimeRangePickerService] 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from "@angular/core/testing"; 2 | import { provideZoneChangeDetection } from "@angular/core"; 3 | 4 | import { NgxDatetimeRangePickerService } from "./ngx-datetime-range-picker.service"; 5 | import { Options, Config, State, Settings } from "./interfaces"; 6 | 7 | declare let require: any; 8 | const moment = require("moment"); 9 | 10 | const DEFAULT_DATE_FORMAT = "YYYY-MM-DD"; 11 | 12 | describe("NgxDatetimeRangePickerService", () => { 13 | let service: NgxDatetimeRangePickerService; 14 | let options: Options; 15 | let settings: Settings; 16 | let config: Config; 17 | let state: State; 18 | 19 | beforeEach(() => { 20 | TestBed.configureTestingModule({ 21 | providers: [provideZoneChangeDetection({ ignoreChangesOutsideZone: true })] 22 | }); 23 | service = TestBed.get(NgxDatetimeRangePickerService); 24 | options = service.getDefaultOptions(); 25 | settings = service.getDefaultSettings(); 26 | config = Object.assign(options, settings); 27 | state = service.getDefaultState(); 28 | }); 29 | 30 | it("should be created", () => { 31 | expect(service).toBeTruthy(); 32 | }); 33 | 34 | it("#getDateCharacteristics", () => { 35 | const date = moment().format(DEFAULT_DATE_FORMAT); 36 | const month = moment().format("MMM YYYY"); 37 | const side = "left"; 38 | 39 | expect(service.getDateCharacteristics(config, state, date, month, side)).toEqual({ 40 | available: true, 41 | inRange: true, 42 | active: true, 43 | today: true 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { 3 | DEFAULT_DATE_FORMAT, 4 | NgxDatetimeRangePickerConstants as Constants 5 | } from "./ngx-datetime-range-picker.constants"; 6 | import { getNotAvailableText, cloneDeep, isNil } from "./ngx-datetime-range-picker.utils"; 7 | import { 8 | Options, 9 | Settings, 10 | CalendarSides, 11 | State, 12 | RowItemVariables, 13 | RowItemOptions, 14 | DateCharacteristics, 15 | Config, 16 | RowOptions, 17 | CalendarTypes, 18 | RowVariables, 19 | CalendarType 20 | } from "./interfaces"; 21 | import { Moment } from "moment"; 22 | 23 | declare let require: any; 24 | const moment = require("moment"); 25 | 26 | const DEFAULT_TIME_FORMAT = Constants.DEFAULT.TIME_FORMAT; 27 | const MONTHS_AVAILABLE = Constants.CONSTANT.MONTHS_AVAILABLE; 28 | const TZ_NAMES = Constants.CONSTANT.TZ_NAMES; 29 | const DEFAULT_RANGES = Constants.DEFAULT.RANGES; 30 | const MOMENT_CONVERSION_MAP = Constants.CONSTANT.MOMENT_CONVERSION_MAP; 31 | 32 | @Injectable({ 33 | providedIn: "root" 34 | }) 35 | export class NgxDatetimeRangePickerService { 36 | getDefaultOptions(): Options { 37 | return cloneDeep(Constants.DEFAULT.OPTIONS) as Options; 38 | } 39 | 40 | getDefaultSettings(): Settings { 41 | return cloneDeep(Constants.DEFAULT.SETTINGS) as Settings; 42 | } 43 | 44 | getDefaultState(): State { 45 | return cloneDeep(Constants.DEFAULT.STATE) as State; 46 | } 47 | 48 | checkSettingsValidity(settings: Settings) { 49 | if (settings.type && !CalendarTypes.includes(settings.type as CalendarType)) { 50 | const errMsg = `${settings.type} is an invalid calendar type. It should one of ${[...CalendarTypes]}`; 51 | throw new Error(errMsg); 52 | } 53 | } 54 | 55 | formatDateToDefaultFormat(date: string | number, format: string): string { 56 | let formattedDate = null; 57 | if (!date) { 58 | return; 59 | } 60 | 61 | if (!isNaN(Number(date))) { 62 | formattedDate = moment(date).format(DEFAULT_DATE_FORMAT); 63 | } else { 64 | formattedDate = moment(date, format).format(DEFAULT_DATE_FORMAT); 65 | } 66 | 67 | return formattedDate; 68 | } 69 | 70 | formatTimeToDefaultFormat(time: string) { 71 | let formattedTime = null; 72 | if (!time) { 73 | return; 74 | } 75 | 76 | if (time.indexOf(":") > -1) { 77 | if (time.indexOf("AM") > -1 || time.indexOf("PM") > -1) { 78 | formattedTime = moment(time, "h:mm A").format(DEFAULT_TIME_FORMAT); 79 | } else { 80 | formattedTime = time; 81 | } 82 | } else { 83 | console.warn( 84 | `WARN_NGX_DATETIME_RANGE_PICKER: 85 | The provided time is not in correct format. 86 | Format: HH:mm or hh:mm A 87 | ` 88 | ); 89 | } 90 | return formattedTime; 91 | } 92 | 93 | getCalendarRowNumberText(type, number) { 94 | return (() => { 95 | switch (type) { 96 | case "daily": 97 | return `W${number}`; 98 | case "weekly": 99 | return ""; 100 | case "monthly": 101 | return `Q${number}`; 102 | case "quarterly": 103 | return `${number}`; 104 | case "yearly": 105 | return ""; 106 | } 107 | })(); 108 | } 109 | 110 | createDefaultRanges(config: Config): object { 111 | const ranges = {}; 112 | const type: string = config.type; 113 | const maxDate: string = cloneDeep(config.maxDate) as string; 114 | 115 | DEFAULT_RANGES[type].forEach((rangeInfo: { label: string; count: number }) => { 116 | ranges[rangeInfo.label] = { 117 | startDate: moment(maxDate, DEFAULT_DATE_FORMAT) 118 | .subtract(rangeInfo.count, MOMENT_CONVERSION_MAP[type]) 119 | .format(DEFAULT_DATE_FORMAT), 120 | endDate: maxDate 121 | }; 122 | }); 123 | 124 | ranges["Custom Range"] = { startDate: null, endDate: null }; 125 | return ranges; 126 | } 127 | 128 | getSanitizedDateArray(config: Config): string[] { 129 | const sanitizedDateArray: string[] = []; 130 | const type = config.type; 131 | const dateArray = config.dateArray; 132 | const inputDateFormat = config.inputDateFormat; 133 | 134 | // dateArray can have nulls 135 | dateArray.forEach((date) => { 136 | if (!date) { 137 | return; 138 | } 139 | 140 | let format: string = null; 141 | 142 | if (isNaN(Number(date))) { 143 | if (inputDateFormat) { 144 | format = inputDateFormat; 145 | } else { 146 | format = moment(date)._f; // moment does not support this 147 | } 148 | } 149 | 150 | if (inputDateFormat !== moment(date)._f) { 151 | console.warn( 152 | `ERR_NGX_DATETIME_RANGE_PICKER: 153 | inputDateFormat !== dateFormat in dateArray. 154 | Converted dates might not be as expected 155 | ` 156 | ); 157 | } 158 | 159 | const value: Moment = format ? moment(date, format) : moment(date); 160 | 161 | if (value) { 162 | const formattedDate = value.endOf(MOMENT_CONVERSION_MAP[type as string]).format(DEFAULT_DATE_FORMAT); 163 | sanitizedDateArray.push(formattedDate); 164 | } else { 165 | console.warn( 166 | `ERR_NGX_DATETIME_RANGE_PICKER: 167 | dateArray values are in unknown format. 168 | Pass the format or pass the dates in known format 169 | ` 170 | ); 171 | } 172 | }); 173 | 174 | return [...new Set(sanitizedDateArray)]; 175 | } 176 | 177 | getNumberOfWeeks(date): number { 178 | if (!date) { 179 | return; 180 | } 181 | 182 | const monthStart: number = moment(date, DEFAULT_DATE_FORMAT) 183 | .startOf("month") 184 | .day(); 185 | const monthEnd: number = Number( 186 | moment(date, DEFAULT_DATE_FORMAT) 187 | .endOf("month") 188 | .format("D") 189 | ); 190 | return Math.ceil((monthStart + monthEnd) / 7); 191 | } 192 | 193 | getYearlyWeekCount(year: string): number { 194 | if (!year) { 195 | return; 196 | } 197 | 198 | const yearStartDate: string = moment(year, "YYYY") 199 | .startOf("year") 200 | .format(DEFAULT_DATE_FORMAT); 201 | const yearEndDate: string = moment(year, "YYYY") 202 | .endOf("year") 203 | .format(DEFAULT_DATE_FORMAT); 204 | const yearEndWeekEndDate: string = moment(yearEndDate, DEFAULT_DATE_FORMAT) 205 | .startOf("week") 206 | .format(DEFAULT_DATE_FORMAT); 207 | const yearStartWeekEndDate: string = moment(yearStartDate, DEFAULT_DATE_FORMAT) 208 | .endOf("week") 209 | .format(DEFAULT_DATE_FORMAT); 210 | 211 | const yearStartWeekNumber: number = this.getWeekNumber(yearStartWeekEndDate) as number; 212 | const yearEndWeekNumber: number = this.getWeekNumber(yearEndWeekEndDate) as number; 213 | 214 | return yearEndWeekNumber - yearStartWeekNumber + 1; 215 | } 216 | 217 | getMonthsAvailable(minDate, maxDate, selectedYear): string[] { 218 | const months: string[] = []; 219 | 220 | if (!minDate || !maxDate || !selectedYear) { 221 | return; 222 | } 223 | 224 | minDate = moment(minDate, DEFAULT_DATE_FORMAT).startOf("month"); 225 | maxDate = moment(maxDate, DEFAULT_DATE_FORMAT).startOf("month"); 226 | 227 | let minDatems: number = moment(minDate, DEFAULT_DATE_FORMAT).valueOf(); 228 | let maxDatems: number = moment(maxDate, DEFAULT_DATE_FORMAT).valueOf(); 229 | const yearStartms: number = moment() 230 | .year(selectedYear) 231 | .startOf("year") 232 | .valueOf(); 233 | const yearEndms: number = moment() 234 | .year(selectedYear) 235 | .endOf("year") 236 | .valueOf(); 237 | 238 | if (minDatems < yearStartms) { 239 | minDatems = yearStartms; 240 | } 241 | if (maxDatems > yearEndms) { 242 | maxDatems = yearEndms; 243 | } 244 | 245 | let minDateMonthNumber: number = moment(minDatems).month(); 246 | const diff: number = moment(maxDatems).diff(moment(minDatems), "months"); 247 | const maxMonths: number = diff < MONTHS_AVAILABLE.length ? diff : MONTHS_AVAILABLE.length; 248 | 249 | for (let i = 0; i <= maxMonths; i++) { 250 | if (minDateMonthNumber >= MONTHS_AVAILABLE.length) { 251 | months.push(MONTHS_AVAILABLE[minDateMonthNumber - MONTHS_AVAILABLE.length]); 252 | } else { 253 | months.push(MONTHS_AVAILABLE[minDateMonthNumber]); 254 | } 255 | minDateMonthNumber++; 256 | } 257 | 258 | return months; 259 | } 260 | 261 | getYearsAvailable(config: Config): string[] { 262 | const minDate: string | number = config ? config.minDate : ""; 263 | const maxDate: string | number = config ? config.maxDate : ""; 264 | const years: string[] = []; 265 | 266 | if (minDate && maxDate) { 267 | const minYear: number = Number(this.getSelectedYear(minDate)); 268 | const maxYear: number = Number(this.getSelectedYear(maxDate)); 269 | const diff = maxYear - minYear; 270 | 271 | for (let i = 0; i <= diff; i++) { 272 | years.push(`${minYear + i}`); 273 | } 274 | } 275 | return years.reverse(); 276 | } 277 | 278 | isDateAvailable( 279 | date: number, 280 | minDate: number, 281 | maxDate: number, 282 | startDate: number, 283 | endDate: number, 284 | monthStartDate: number, 285 | monthEndDate: number, 286 | config: Config 287 | ): boolean { 288 | let available = false; 289 | const type: string = config.type; 290 | const disableWeekends: boolean = config.disableWeekends; 291 | const disableWeekdays: boolean = config.disableWeekdays; 292 | 293 | if (type === "daily") { 294 | minDate = minDate > monthStartDate ? minDate : monthStartDate; 295 | maxDate = maxDate < monthEndDate ? maxDate : monthEndDate; 296 | } 297 | 298 | if (date >= minDate && date <= maxDate) { 299 | available = true; 300 | 301 | if (available) { 302 | if (disableWeekends) { 303 | available = !this.isWeekend(date); 304 | } 305 | if (disableWeekdays) { 306 | available = !this.isWeekday(date); 307 | } 308 | if (config.dateArray.length) { 309 | available = this.isInDateArray(date, config.dateArray, DEFAULT_DATE_FORMAT); 310 | } 311 | } 312 | } 313 | return available; 314 | } 315 | 316 | isDateInRange( 317 | date: number, 318 | minDate: number, 319 | maxDate: number, 320 | startDate: number, 321 | endDate: number, 322 | monthStartDate: number, 323 | monthEndDate: number, 324 | available: boolean, 325 | config: Config 326 | ): boolean { 327 | let inRange = false; 328 | const type: string = config.type; 329 | const singleDatePicker: boolean = config.singleDatePicker; 330 | 331 | if (!singleDatePicker) { 332 | if (type === "daily") { 333 | minDate = monthStartDate; 334 | maxDate = monthEndDate; 335 | } 336 | if (date >= startDate && date <= endDate && date >= minDate && date <= maxDate) { 337 | if (available) { 338 | inRange = true; 339 | } 340 | } 341 | } 342 | return inRange; 343 | } 344 | 345 | isDateActive(date: number, startDate: number, endDate: number, side: string): boolean { 346 | return (date === startDate && side === "left") || (date === endDate && side === "right"); 347 | } 348 | 349 | isDateToday(dateMs: number, config): boolean { 350 | const todayDate: string = moment().format(DEFAULT_DATE_FORMAT); 351 | const type: string = config.type; 352 | const { firstDay, lastDay } = this.getFirstLastDay(todayDate, type); 353 | const firstDayMs: number = moment(firstDay, DEFAULT_DATE_FORMAT).valueOf(); 354 | const lastDayMs: number = moment(lastDay, DEFAULT_DATE_FORMAT).valueOf(); 355 | return dateMs >= firstDayMs && dateMs <= lastDayMs; 356 | } 357 | 358 | isWeekday(date: number, format?: string): boolean { 359 | return !this.isWeekend(date, format); 360 | } 361 | 362 | isWeekend(date: number, format?: string): boolean { 363 | if (!format) { 364 | format = null; 365 | } 366 | const day = moment(date, format).day(); 367 | return day === 0 || day === 6; 368 | } 369 | 370 | isInDateArray(date: number, dateArray: any[], format?: string): boolean { 371 | if (!format) { 372 | format = null; 373 | } 374 | return dateArray.find((d) => moment(d, format).valueOf() === date) !== undefined; 375 | } 376 | 377 | getCalendarRowVariables(options: RowOptions): RowVariables { 378 | const variables: RowVariables = { 379 | rowNumber: "", 380 | columns: 0 381 | }; 382 | const type: string = options.type; 383 | const monthStartWeekNumber: number = options.monthStartWeekNumber; 384 | const dateRows: number = options.dateRows; 385 | const year = `${options.year}`; 386 | 387 | if (type === "daily") { 388 | variables.rowNumber = `${monthStartWeekNumber + dateRows}`; 389 | variables.columns = 6; 390 | } else if (type === "weekly") { 391 | variables.rowNumber = ``; 392 | variables.columns = 6; 393 | } else if (type === "monthly") { 394 | variables.rowNumber = `${dateRows + 1}`; 395 | variables.columns = 2; 396 | } else if (type === "quarterly") { 397 | variables.rowNumber = year.charAt(dateRows); 398 | variables.columns = 0; 399 | } else if (type === "yearly") { 400 | variables.rowNumber = ""; 401 | variables.columns = 0; 402 | } 403 | 404 | return variables; 405 | } 406 | 407 | getCalendarRowItemVariables(options: RowItemOptions): RowItemVariables { 408 | const { type, monthStartWeekNumber, yearStartDate, year, rowItem, dateRows, columns } = options; 409 | 410 | const itemCount: number = rowItem + dateRows * columns + dateRows; 411 | let currentItemDate = ""; 412 | let rowItemText = ""; 413 | 414 | if (type === "daily") { 415 | if (!isNil(monthStartWeekNumber) && !isNil(dateRows) && !isNil(year)) { 416 | const yearStartDateDaily = moment() 417 | .year(year) 418 | .startOf("year") 419 | .format(DEFAULT_DATE_FORMAT); 420 | currentItemDate = moment(yearStartDateDaily, DEFAULT_DATE_FORMAT) 421 | .add(monthStartWeekNumber + dateRows - 1, "week") 422 | .startOf("week") 423 | .add(rowItem, "day") 424 | .format(DEFAULT_DATE_FORMAT); 425 | rowItemText = moment(currentItemDate, DEFAULT_DATE_FORMAT).format("D"); 426 | } 427 | } else if (type === "weekly") { 428 | if (!isNil(yearStartDate) && !isNil(itemCount)) { 429 | currentItemDate = moment(yearStartDate, DEFAULT_DATE_FORMAT) 430 | .add(itemCount, "week") 431 | .endOf("week") 432 | .format(DEFAULT_DATE_FORMAT); 433 | const weekNumber: any = itemCount + 1; 434 | rowItemText = `W${weekNumber}`; 435 | } 436 | } else if (type === "monthly") { 437 | if (!isNil(itemCount) && !isNil(year)) { 438 | currentItemDate = moment() 439 | .year(year) 440 | .month(itemCount) 441 | .endOf("month") 442 | .format(DEFAULT_DATE_FORMAT); 443 | rowItemText = moment(currentItemDate, DEFAULT_DATE_FORMAT).format("MMM"); 444 | } 445 | } else if (type === "quarterly") { 446 | if (!isNil(itemCount) && !isNil(year)) { 447 | currentItemDate = moment() 448 | .year(year) 449 | .quarter(itemCount + 1) 450 | .endOf("quarter") 451 | .format(DEFAULT_DATE_FORMAT); 452 | rowItemText = `Quarter ${itemCount + 1}`; 453 | } 454 | } 455 | 456 | const { firstDay, lastDay } = this.getFirstLastDay(currentItemDate, type); 457 | 458 | return { 459 | itemCount, 460 | currentItemDate, 461 | rowItemText, 462 | firstDay, 463 | lastDay 464 | }; 465 | } 466 | 467 | isRowIemValid(options: RowOptions): boolean { 468 | let valid = false; 469 | const type: string = options.type; 470 | const year: string = options.year; 471 | const itemCount: number = options.itemCount; 472 | const validWeekCount: number = this.getYearlyWeekCount(year); 473 | 474 | if (type === "daily") { 475 | valid = true; 476 | } else if (type === "weekly") { 477 | if (itemCount < validWeekCount) { 478 | valid = true; 479 | } 480 | } else if (type === "monthly") { 481 | valid = true; 482 | } else if (type === "quarterly") { 483 | valid = true; 484 | } 485 | 486 | return valid; 487 | } 488 | 489 | formatStartDate(config: Config, returnFormat: string): string { 490 | const startDate: string | number = config ? config.startDate : null; 491 | const type: string = config ? config.type : ""; 492 | let formattedStartDate: string = null; 493 | 494 | if (startDate) { 495 | formattedStartDate = moment(startDate, DEFAULT_DATE_FORMAT) 496 | .startOf(MOMENT_CONVERSION_MAP[type]) 497 | .format(returnFormat); 498 | } 499 | 500 | return formattedStartDate; 501 | } 502 | 503 | getSelectedYear(date: string | number): number { 504 | return moment(date, DEFAULT_DATE_FORMAT).format("YYYY"); 505 | } 506 | 507 | getFirstLastDay(date: string, type: string): { firstDay: string; lastDay: string } { 508 | let firstDay = ""; 509 | let lastDay = ""; 510 | 511 | if (type === "daily") { 512 | firstDay = lastDay = date; 513 | } else if (type === "weekly") { 514 | firstDay = moment(date, DEFAULT_DATE_FORMAT) 515 | .startOf("week") 516 | .format(DEFAULT_DATE_FORMAT); 517 | lastDay = moment(date, DEFAULT_DATE_FORMAT) 518 | .endOf("week") 519 | .format(DEFAULT_DATE_FORMAT); 520 | } else if (type === "monthly") { 521 | firstDay = moment(date, DEFAULT_DATE_FORMAT) 522 | .startOf("month") 523 | .format(DEFAULT_DATE_FORMAT); 524 | lastDay = moment(date, DEFAULT_DATE_FORMAT) 525 | .endOf("month") 526 | .format(DEFAULT_DATE_FORMAT); 527 | } else if (type === "quarterly") { 528 | firstDay = moment(date, DEFAULT_DATE_FORMAT) 529 | .startOf("quarter") 530 | .format(DEFAULT_DATE_FORMAT); 531 | lastDay = moment(date, DEFAULT_DATE_FORMAT) 532 | .endOf("quarter") 533 | .format(DEFAULT_DATE_FORMAT); 534 | } else if (type === "yearly") { 535 | firstDay = moment(date, DEFAULT_DATE_FORMAT) 536 | .startOf("year") 537 | .format(DEFAULT_DATE_FORMAT); 538 | lastDay = moment(date, DEFAULT_DATE_FORMAT) 539 | .endOf("year") 540 | .format(DEFAULT_DATE_FORMAT); 541 | } 542 | 543 | return { firstDay, lastDay }; 544 | } 545 | 546 | getZoneDate(tz: string, format: string, date?: string): Moment { 547 | let _date: number = moment().valueOf(); 548 | 549 | if (date) { 550 | _date = moment(date, format) 551 | .startOf("day") 552 | .valueOf(); 553 | } 554 | 555 | const today = new Date(_date).toLocaleString("en-US", { 556 | timeZone: TZ_NAMES[tz] 557 | }); 558 | 559 | return moment(today, "MM/DD/YYYY, hh:mm:ss A"); 560 | } 561 | 562 | getZoneToday(tz: string, viewDateFormat: string): string { 563 | const today: Moment = this.getZoneDate(tz, viewDateFormat); 564 | return moment(today).format(`${viewDateFormat} hh:mm A`); 565 | } 566 | 567 | formatToZoneDate(tz: string, format: string, date: string): string { 568 | const formattedDate: Moment = this.getZoneDate(tz, format, date); 569 | return moment(formattedDate).format(`${format}`); 570 | } 571 | 572 | convertToViewTimeItem(item: string | number): string { 573 | let stringified_item = item + ""; 574 | if (stringified_item.length === 1) { 575 | stringified_item = `0${stringified_item}`; 576 | } 577 | return stringified_item; 578 | } 579 | 580 | getWeekNumber(date: string): string | number { 581 | if (date) { 582 | const year: number = moment(date, "YYYY-MM-DD").year(); 583 | const month: number = moment(date, "YYYY-MM-DD").month(); 584 | const day: number = Number(moment(date, "YYYY-MM-DD").format("D")); 585 | 586 | const yearStartms: Date = new Date(year, 0, 1); 587 | const datems: Date = new Date(year, month, day); 588 | return Math.ceil(((datems.getTime() - yearStartms.getTime()) / 86400000 + yearStartms.getDay() + 1) / 7); 589 | } else { 590 | console.warn(` 591 | WARN_NGX_DATETIME_RANGE_PICKER | getWeekNumber: 592 | Invalid date 593 | `); 594 | return getNotAvailableText(); 595 | } 596 | } 597 | 598 | iterateOverDateObj(dates: CalendarSides, func) { 599 | for (const side in dates) { 600 | if (side) { 601 | const sideDates = dates[side]; 602 | sideDates.itemRows.forEach((rows) => { 603 | rows.items.forEach((rowItem) => { 604 | func(rowItem); 605 | }); 606 | }); 607 | } 608 | } 609 | } 610 | 611 | getCalendarColspan(type: string): number { 612 | if (type === "daily") { 613 | return 6; 614 | } else if (type === "weekly") { 615 | return 8; 616 | } else if (type === "monthly") { 617 | return 3; 618 | } else if (type === "quarterly") { 619 | return 1; 620 | } else if (type === "yearly") { 621 | return 1; 622 | } 623 | } 624 | 625 | getCalendarRowItemColspan(type: string): number { 626 | if (type === "monthly") { 627 | return 3; 628 | } else if (type === "quarterly") { 629 | return 6; 630 | } else if (type === "yearly") { 631 | return 6; 632 | } 633 | } 634 | 635 | getDateCharacteristics(config: Config, state: State, date: string, month: string, side: string): DateCharacteristics { 636 | const currentDate: number = moment(date, DEFAULT_DATE_FORMAT) 637 | .startOf("day") 638 | .valueOf(); 639 | 640 | let _date: string = this.formatDateToDefaultFormat(config.minDate, DEFAULT_DATE_FORMAT); 641 | const minDate: number = moment(_date, DEFAULT_DATE_FORMAT) 642 | .startOf("day") 643 | .valueOf(); 644 | 645 | _date = this.formatDateToDefaultFormat(config.maxDate, DEFAULT_DATE_FORMAT); 646 | const maxDate: number = moment(_date, DEFAULT_DATE_FORMAT) 647 | .startOf("day") 648 | .valueOf(); 649 | 650 | _date = this.formatDateToDefaultFormat(config.startDate, DEFAULT_DATE_FORMAT); 651 | const startDate: number = moment(_date, DEFAULT_DATE_FORMAT) 652 | .startOf("day") 653 | .valueOf(); 654 | 655 | _date = this.formatDateToDefaultFormat(config.endDate, DEFAULT_DATE_FORMAT); 656 | const endDate: number = moment(_date, DEFAULT_DATE_FORMAT) 657 | .startOf("day") 658 | .valueOf(); 659 | 660 | const currentMonthStartDate: number = moment(month, "MMM YYYY") 661 | .startOf("month") 662 | .startOf("day") 663 | .valueOf(); 664 | const currentMonthEndDate: number = moment(month, "MMM YYYY") 665 | .endOf("month") 666 | .startOf("day") 667 | .valueOf(); 668 | 669 | const available: boolean = this.isDateAvailable( 670 | currentDate, 671 | minDate, 672 | maxDate, 673 | startDate, 674 | endDate, 675 | currentMonthStartDate, 676 | currentMonthEndDate, 677 | config 678 | ); 679 | const inRange: boolean = this.isDateInRange( 680 | currentDate, 681 | minDate, 682 | maxDate, 683 | startDate, 684 | endDate, 685 | currentMonthStartDate, 686 | currentMonthEndDate, 687 | available, 688 | config 689 | ); 690 | const active: boolean = this.isDateActive(currentDate, startDate, endDate, side); 691 | const today: boolean = this.isDateToday(currentDate, config); 692 | 693 | // Active 694 | if (currentDate === startDate && side === "left") { 695 | state.activeStartDate = date; 696 | } else if (currentDate === endDate && side === "right") { 697 | state.activeEndDate = date; 698 | } 699 | 700 | return { available, inRange, active, today }; 701 | } 702 | 703 | getLabelProps( 704 | state: State, 705 | calendarType: string, 706 | side: string 707 | ): { label: string; labelFormat: string; type: string } { 708 | let label: string, labelFormat: string, type: string; 709 | 710 | if (calendarType === "daily") { 711 | label = `${state.selectedMonth[side]} ${state.selectedYear[side]}`; 712 | labelFormat = "MMM YYYY"; 713 | type = "month"; 714 | } else { 715 | label = `${state.selectedYear[side]}`; 716 | labelFormat = "YYYY"; 717 | type = "year"; 718 | } 719 | 720 | return { label, labelFormat, type }; 721 | } 722 | } 723 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.utils.ts: -------------------------------------------------------------------------------- 1 | export const getNotAvailableText = (): string => { 2 | return "N/A"; 3 | }; 4 | 5 | /** 6 | * 7 | * @param value the value to be cloned 8 | * @note will not work for objects containing functions 9 | */ 10 | export const cloneDeep = (value: object | string | number): object | string | number => { 11 | if (value) { 12 | return JSON.parse(JSON.stringify(value)); 13 | } 14 | }; 15 | 16 | export const isEmpty = (value: object): boolean => { 17 | if (value) { 18 | return Object.keys(value).length <= 0; 19 | } 20 | }; 21 | 22 | export const mergeDeep = (...objects): object => { 23 | const isObject = (obj) => obj && typeof obj === "object"; 24 | 25 | return objects.reduce((prev, obj) => { 26 | Object.keys(obj).forEach((key) => { 27 | const pVal = prev[key]; 28 | const oVal = obj[key]; 29 | 30 | if (Array.isArray(pVal) && Array.isArray(oVal)) { 31 | prev[key] = pVal.concat(...oVal); 32 | } else if (isObject(pVal) && isObject(oVal)) { 33 | prev[key] = mergeDeep(pVal, oVal); 34 | } else { 35 | prev[key] = oVal; 36 | } 37 | }); 38 | 39 | return prev; 40 | }, {}); 41 | }; 42 | 43 | export const isNil = (value) => { 44 | return value == null || value === undefined; 45 | }; 46 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/lib/pipes/objNgFor.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | /** 4 | * Iterate over {key: value} 5 | * Returns the keys of the object 6 | * Usage: 7 | * let objKey of obj | ObjNgFor 8 | * Example: 9 | * let obj = {a: 1, b: 2}; 10 | * *ngFor="let key of obj | ObjNgFor" 11 | * {{keys}}: {{obj[key]}} 12 | */ 13 | 14 | @Pipe({ 15 | name: "ObjNgFor", 16 | pure: false, 17 | standalone: false 18 | }) 19 | export class ObjNgFor implements PipeTransform { 20 | public transform(value: any): any { 21 | return Object.keys(value); // .map(key => value[key]); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of ngx-datetime-range-picker 3 | */ 4 | 5 | export * from "./lib/ngx-datetime-range-picker.service"; 6 | export * from "./lib/ngx-datetime-range-picker.component"; 7 | export * from "./lib/ngx-datetime-range-picker.module"; 8 | export * from "./lib/material/material.module"; 9 | export * from "./lib/interfaces"; 10 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import "core-js/es7/reflect"; 4 | import "zone.js/dist/zone"; 5 | import "zone.js/dist/zone-testing"; 6 | import { getTestBed } from "@angular/core/testing"; 7 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing"; 8 | 9 | declare const require: any; 10 | 11 | // First, initialize the Angular testing environment. 12 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 13 | // Then we find all the tests. 14 | const context = require.context("./", true, /\.spec\.ts$/); 15 | // And load the modules. 16 | context.keys().map(context); 17 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": ["dom", "es2015", "es2017", "ESNext"] 16 | }, 17 | "angularCompilerOptions": { 18 | "skipTemplateCodegen": true, 19 | "strictMetadataEmit": true, 20 | "fullTemplateTypeCheck": true, 21 | "strictInjectionParameters": true, 22 | "enableResourceInlining": true 23 | }, 24 | "exclude": ["src/test.ts", "**/*.spec.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "angularCompilerOptions": { 4 | "enableIvy": true, 5 | "compilationMode": "partial" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["src/test.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /projects/ngx-datetime-range-picker/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

ngx-datetime-range-picker

4 |
5 |
6 |
7 | 13 | {{ option.name }} 14 | 15 |
16 | 17 |
18 | 24 | 25 | {{ componentType.name }} 26 | 27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | 35 | Single select 36 | 37 | 38 | 47 | 48 |
54 |
55 |
56 |
57 |
58 | 59 | 60 | Date range 61 | 62 | 63 | 72 | 73 |
79 |
80 |
81 |
82 |
83 | 84 | 85 | Date array range 86 | 87 | 88 | 97 | 98 |
108 |
109 |
110 |
111 |
112 | 113 | 114 | Date time range 115 | 116 | 117 | 126 | 127 |
137 |
138 |
139 |
140 |
141 | 142 | 143 | Date time range with timezone 144 | 145 | 146 | 155 | 156 |
166 |
167 |
168 |
169 |
170 |
171 | 172 | 173 |
174 | 175 |
Settings:
176 |
{{ printSettings(settings) }}
177 | 178 |
Config:
179 |
{{ printSettings(config) }}
180 | 181 |
Selected:
182 |
{{ printDate(date) }}
183 |
184 |
185 |
186 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .date-container { 2 | padding: 20px; 3 | 4 | .app-name { 5 | width: 100%; 6 | display: flex; 7 | justify-content: center; 8 | } 9 | 10 | .selectors { 11 | display: flex; 12 | flex-direction: column; 13 | gap: 16px; 14 | } 15 | 16 | .component-level-selector, 17 | .component-type-selector { 18 | width: 100%; 19 | display: flex; 20 | justify-content: center; 21 | } 22 | 23 | .list-inline, 24 | .list-unstyled { 25 | padding-left: 0; 26 | list-style: none; 27 | } 28 | 29 | .filter-container { 30 | width: 100%; 31 | padding: 20px 0; 32 | display: flex; 33 | gap: 16px; 34 | flex-wrap: wrap; 35 | 36 | .filter { 37 | color: #333; 38 | width: 100%; 39 | 40 | .filter-card { 41 | height: 100%; 42 | 43 | .selectedDate { 44 | margin-top: 8px; 45 | 46 | .display-divider { 47 | margin: 8px 0; 48 | } 49 | 50 | pre { 51 | margin: 5px 0; 52 | background: #f5f5f5; 53 | border-radius: 8px; 54 | padding: 8px; 55 | } 56 | } 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, ComponentFixture, inject } from "@angular/core/testing"; 2 | import { NO_ERRORS_SCHEMA, provideZoneChangeDetection } from "@angular/core"; 3 | 4 | import { AppComponent } from "./app.component"; 5 | import { SharedService } from "src/common/services/shared.service"; 6 | 7 | describe("AppComponent", () => { 8 | let component: AppComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [AppComponent], 14 | schemas: [NO_ERRORS_SCHEMA], 15 | providers: [provideZoneChangeDetection({ ignoreChangesOutsideZone: true }), SharedService] 16 | }).compileComponents(); 17 | }); 18 | 19 | beforeEach(() => { 20 | fixture = TestBed.createComponent(AppComponent); 21 | component = fixture.debugElement.componentInstance; 22 | fixture.detectChanges(); 23 | }); 24 | 25 | it("should create the app", () => { 26 | expect(component).toBeDefined(); 27 | }); 28 | 29 | it("printDate", () => { 30 | component.selectedOption = "daily"; 31 | const json = { daily: { a: 1 } }; 32 | expect(component.printDate(json)).toBe(JSON.stringify(json[component.selectedOption], null, 4).trim()); 33 | }); 34 | 35 | it(`should save route data params`, inject([SharedService], (ss: SharedService) => { 36 | console.log(ss); 37 | // expect(component.ss.currentRouteParams).toBeDefined(); 38 | })); 39 | }); 40 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | import { Options, Settings, DateRangeModel } from "../../projects/ngx-datetime-range-picker/src/lib/interfaces"; 3 | import moment from "moment"; 4 | 5 | const DEFAULT_DATE_FORMAT = "YYYY-MM-DD"; 6 | 7 | @Component({ 8 | selector: "app-root", 9 | templateUrl: "./app.component.html", 10 | styleUrls: ["./app.component.scss"], 11 | standalone: false 12 | }) 13 | export class AppComponent { 14 | public selectedOption = "daily"; 15 | public options = [ 16 | { name: "Daily", value: "daily" }, 17 | { name: "Weekly", value: "weekly" }, 18 | { name: "Monthly", value: "monthly" }, 19 | { name: "Quarterly", value: "quarterly" }, 20 | { name: "Yearly", value: "yearly" } 21 | ]; 22 | public componentTypes = [ 23 | { name: "Single Date Picker", value: "singleDatePicker" }, 24 | { name: "Date Range Picker", value: "dateRangePicker" }, 25 | { name: "Date Array Range Picker", value: "dateArrayRangePicker" }, 26 | { name: "Date Time Range Picker", value: "dateTimeRangePicker" }, 27 | { name: "Date Time Range Picker with Timezone", value: "dateTimeRangeWithTimeZonePicker" } 28 | ]; 29 | 30 | public selectedOptions: any = { 31 | date: { 32 | daily: { 33 | startDate: moment().format(DEFAULT_DATE_FORMAT), 34 | endDate: moment().format(DEFAULT_DATE_FORMAT) 35 | }, 36 | weekly: { 37 | startDate: moment() 38 | .subtract(6, "days") 39 | .format(DEFAULT_DATE_FORMAT), 40 | endDate: moment().format(DEFAULT_DATE_FORMAT) 41 | } as Options 42 | } as DateRangeModel, 43 | dateRange: { 44 | daily: { 45 | startDate: moment() 46 | .subtract(6, "days") 47 | .format(DEFAULT_DATE_FORMAT), 48 | endDate: moment().format(DEFAULT_DATE_FORMAT) 49 | }, 50 | weekly: { 51 | startDate: moment() 52 | .subtract(2, "weeks") 53 | .format(DEFAULT_DATE_FORMAT), 54 | endDate: moment().format(DEFAULT_DATE_FORMAT) 55 | } as Options 56 | } as DateRangeModel, 57 | dateArrayRange: { 58 | daily: { 59 | dateArray: [ 60 | moment() 61 | .subtract(1, "weeks") 62 | .format(DEFAULT_DATE_FORMAT), 63 | moment() 64 | .subtract(3, "weeks") 65 | .format(DEFAULT_DATE_FORMAT), 66 | moment() 67 | .subtract(5, "weeks") 68 | .format(DEFAULT_DATE_FORMAT) 69 | ], 70 | minDate: moment() 71 | .subtract(6, "weeks") 72 | .format(DEFAULT_DATE_FORMAT), 73 | maxDate: moment().format(DEFAULT_DATE_FORMAT) 74 | }, 75 | weekly: { 76 | dateArray: [ 77 | moment().format(DEFAULT_DATE_FORMAT), 78 | moment() 79 | .subtract(3, "weeks") 80 | .format(DEFAULT_DATE_FORMAT), 81 | moment() 82 | .subtract(5, "weeks") 83 | .format(DEFAULT_DATE_FORMAT) 84 | ], 85 | minDate: moment() 86 | .subtract(6, "weeks") 87 | .format(DEFAULT_DATE_FORMAT), 88 | maxDate: moment().format(DEFAULT_DATE_FORMAT) 89 | } as Options 90 | } as DateRangeModel, 91 | dateTimeRange: { 92 | daily: { 93 | startDate: moment() 94 | .subtract(6, "weeks") 95 | .format(DEFAULT_DATE_FORMAT), 96 | endDate: moment().format(DEFAULT_DATE_FORMAT), 97 | minDate: "2017-01-01", 98 | maxDate: moment().format(DEFAULT_DATE_FORMAT), 99 | startTime: "13:00", 100 | endTime: "18:00" 101 | } as Options 102 | } as DateRangeModel, 103 | dateTimeWithTimezoneRange: { 104 | daily: { 105 | startDate: moment() 106 | .subtract(6, "weeks") 107 | .format(DEFAULT_DATE_FORMAT), 108 | endDate: moment().format(DEFAULT_DATE_FORMAT), 109 | minDate: "2017-01-01", 110 | maxDate: moment().format(DEFAULT_DATE_FORMAT), 111 | startTime: "13:00", 112 | endTime: "18:00" 113 | } as Options 114 | } as DateRangeModel 115 | }; 116 | 117 | public selectedComponentType = "singleDatePicker"; 118 | 119 | public config: any = JSON.parse(JSON.stringify(this.selectedOptions)); 120 | public datePickerOptions: Options = {}; 121 | public datePickerSettings: Settings = this.getDateSettings(); 122 | public dateRangePickerOptions: Options = {}; 123 | public dateRangePickerSettings: Settings = this.getDateRangeSettings(); 124 | public dateArrayRangePickerOptions: Options = {}; 125 | public dateArrayRangePickerSettings: Settings = this.getDateArrayRangeSettings(); 126 | public dateTimeRangePickerSettings: Settings = this.getDateTimeRangeSettings(); 127 | public dateTimeRangeWithTimeZonePickerOptions: Settings = this.getDateTimeRangeWithTimeZoneSettings(); 128 | 129 | public onFilterChange(event, filter) { 130 | if (typeof event.defaultPrevented !== "undefined") { 131 | event.preventDefault(); 132 | } 133 | if (filter === "level") { 134 | this.datePickerSettings = this.getDateSettings(); 135 | this.datePickerSettings.type = event.value; 136 | this.dateRangePickerSettings = this.getDateRangeSettings(); 137 | this.dateRangePickerSettings.type = event.value; 138 | this.dateArrayRangePickerSettings = this.getDateArrayRangeSettings(); 139 | this.dateArrayRangePickerSettings.type = event.value; 140 | this.dateTimeRangePickerSettings = this.getDateTimeRangeSettings(); 141 | this.dateTimeRangePickerSettings.type = event.value; 142 | this.dateTimeRangeWithTimeZonePickerOptions = this.getDateTimeRangeWithTimeZoneSettings(); 143 | this.dateTimeRangeWithTimeZonePickerOptions.type = event.value; 144 | } 145 | } 146 | 147 | public getDateSettings(): Settings { 148 | return { 149 | singleDatePicker: true, 150 | displayEndDate: true, 151 | retailCalendar: false, 152 | timezoneSupport: false, 153 | type: this.selectedOption, 154 | viewDateFormat: "MMM D, YYYY", 155 | label: "Date", 156 | placeholder: "Date", 157 | inputDateFormat: "YYYY-MM-DD", 158 | showRowNumber: true 159 | }; 160 | } 161 | 162 | public getDateRangeSettings(): Settings { 163 | return { 164 | retailCalendar: false, 165 | timezoneSupport: false, 166 | type: this.selectedOption, 167 | viewDateFormat: "MMM D, YYYY", 168 | label: "Date Range", 169 | placeholder: "Date Range", 170 | inputDateFormat: "YYYY-MM-DD" 171 | }; 172 | } 173 | 174 | public getDateArrayRangeSettings(): Settings { 175 | return { 176 | retailCalendar: false, 177 | timezoneSupport: false, 178 | type: this.selectedOption, 179 | viewDateFormat: "MMM D, YYYY", 180 | label: "Date Array Range", 181 | placeholder: "Date Array Range", 182 | inputDateFormat: "YYYY-MM-DD" 183 | }; 184 | } 185 | 186 | public getDateTimeRangeSettings(): Settings { 187 | return { 188 | retailCalendar: false, 189 | timezoneSupport: false, 190 | timePicker: true, 191 | type: this.selectedOption, 192 | viewDateFormat: "MMM D, YYYY", 193 | label: "Date Time Range", 194 | placeholder: "Date Time Range", 195 | inputDateFormat: "YYYY-MM-DD" 196 | }; 197 | } 198 | 199 | public getDateTimeRangeWithTimeZoneSettings(): Settings { 200 | return { 201 | retailCalendar: false, 202 | timezoneSupport: true, 203 | timePicker: true, 204 | type: this.selectedOption, 205 | viewDateFormat: "MMM D, YYYY", 206 | label: "Date Time Range", 207 | placeholder: "Date Time Range", 208 | inputDateFormat: "YYYY-MM-DD" 209 | }; 210 | } 211 | 212 | public printDate(date): string { 213 | if (!date || !date[this.selectedOption]) { 214 | return; 215 | } 216 | return JSON.stringify(date[this.selectedOption], null, 4).trim(); 217 | } 218 | 219 | public printSettings(settings): string { 220 | if (!settings) { 221 | return; 222 | } 223 | return JSON.stringify(settings, null, 4).trim(); 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { Config as baseConfig } from "../config/config.base"; 2 | import { Config as envConfig } from "../config/config"; 3 | 4 | export const AppConfig: any = { 5 | ...baseConfig, 6 | ...envConfig 7 | }; 8 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Injector } from "@angular/core"; 2 | import { BrowserModule } from "@angular/platform-browser"; 3 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 4 | import { MatButtonToggleModule } from "@angular/material/button-toggle"; 5 | import { MatCardModule } from "@angular/material/card"; 6 | import { MatDividerModule } from "@angular/material/divider"; 7 | 8 | import { AppInitService } from "src/common/services/app.init.service"; 9 | import { InjectorService } from "src/common/services/injector.service"; 10 | import { SharedService } from "src/common/services/shared.service"; 11 | 12 | import { NgxDatetimeRangePickerModule } from "ngx-datetime-range-picker"; 13 | // import { NgxDatetimeRangePickerModule } from "../../projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.module"; 14 | 15 | import { MaterialModule } from "../../projects/ngx-datetime-range-picker/src/lib/material/material.module"; 16 | 17 | import { AppComponent } from "./app.component"; 18 | import { provideAnimationsAsync } from "@angular/platform-browser/animations/async"; 19 | 20 | @NgModule({ 21 | declarations: [AppComponent], 22 | imports: [ 23 | BrowserModule, 24 | BrowserAnimationsModule, 25 | NgxDatetimeRangePickerModule.forRoot(), 26 | MaterialModule, 27 | MatButtonToggleModule, 28 | MatCardModule, 29 | MatDividerModule 30 | ], 31 | providers: [AppInitService, SharedService, provideAnimationsAsync()], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class AppModule { 35 | constructor(private injector: Injector) { 36 | InjectorService.injector = this.injector; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/app/app.theme.scss: -------------------------------------------------------------------------------- 1 | @use 'sass:map'; 2 | @use "@angular/material" as mat; 3 | // @use "@angular/material/_index.scss"; 4 | @use "ngx-datetime-range-picker/ngx-datetime-range-picker.component.scss" as ndtrp; 5 | // @use "../../projects/ngx-datetime-range-picker/src/lib/ngx-datetime-range-picker.component.scss" as ndtrp; 6 | @include mat.elevation-classes(); 7 | @include mat.app-background(); 8 | @include mat.core(); 9 | 10 | $ndtrp-primary: mat.$m2-blue-palette; 11 | 12 | // Plus imports for other components in your app. 13 | 14 | // Define a custom typography config that overrides the font-family as well as the 15 | // `headlines` and `body-1` levels. 16 | $app-typography: mat.m2-define-typography-config( 17 | $font-family: "Helvetica", 18 | $body-1: mat.m2-define-typography-level(16px, 20px, 400) 19 | ); 20 | 21 | // Include the common styles for Angular Material. We include this here so that you only 22 | // have to load a single css file for Angular Material in your app. 23 | // Be sure that you only ever include this mixin once! 24 | // @include mat-core($app-typography); 25 | @include mat.typography-hierarchy($app-typography); 26 | 27 | // Define the palettes for your theme using the Material Design palettes available in palette.scss 28 | // (imported above). For each palette, you can optionally specify a default, lighter, and darker 29 | // hue. Available color palettes: https://material.io/design/color/ 30 | $app-primary: mat.m2-define-palette($ndtrp-primary, A200); //colors most widely used across all screens and components 31 | $app-accent: mat.m2-define-palette( 32 | $ndtrp-primary, 33 | A200, 34 | A100, 35 | A400 36 | ); //colors used for the floating action button and interactive elements 37 | 38 | // The warn palette is optional (defaults to red). 39 | $app-warn: mat.m2-define-palette($ndtrp-primary, A200); //colors used to convey error state 40 | 41 | // Create the theme object (a Sass map containing all of the palettes). 42 | $app-theme: mat.m2-define-light-theme( 43 | ( 44 | color: ( 45 | primary: $app-primary, 46 | accent: $app-accent, 47 | warn: $app-warn 48 | ), 49 | typography: $app-typography, 50 | density: 0 51 | ) 52 | ); 53 | 54 | :root { 55 | --mat-standard-button-toggle-shape: var(--mat-sys-corner-full); 56 | --mat-standard-button-toggle-divider-color: var(--mat-sys-outline); 57 | } 58 | 59 | // Include theme styles for core and each component used in your app. 60 | // Alternatively, you can import and @include the theme mixins for each component 61 | // that you are using. 62 | @include mat.elevation-classes(); 63 | @include mat.app-background(); 64 | // @include mat.form-field-theme($app-theme); 65 | // @include mat.icon-theme($app-theme); 66 | // @include mat.button-theme($app-theme); 67 | // @include mat.select-theme($app-theme); 68 | @include mat.all-component-themes($app-theme); 69 | 70 | // library's theme 71 | // @include mat.form-field-theme(ndtrp.$ndtrp-app-theme); 72 | // @include mat.icon-theme(ndtrp.$ndtrp-app-theme); 73 | // @include mat.button-theme(ndtrp.$ndtrp-app-theme); 74 | // @include mat.select-theme(ndtrp.$ndtrp-app-theme); 75 | 76 | // SCSS 77 | @include ndtrp.ngx-datetime-range-picker-theme($app-theme); 78 | 79 | // CSS 80 | // @import "ngx-datetime-range-picker/ngx-datetime-range-picker.theme.css"; 81 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BhavinPatel04/ngx-datetime-range-picker/51ef6a77541925f72acd66d94e5df6cd071e94d0/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/common/page.component.ts: -------------------------------------------------------------------------------- 1 | import { AppConfig } from "../app/app.config"; 2 | import { InjectorService } from "./services/injector.service"; 3 | import { AppInitService } from "./services/app.init.service"; 4 | import { SharedService } from "./services/shared.service"; 5 | 6 | export class BasePageComponent { 7 | ss: SharedService; 8 | data: any = {}; 9 | state: any = {}; 10 | 11 | constructor() { 12 | const appInit = InjectorService.injector.get(AppInitService); 13 | this.ss = appInit.ss; 14 | appInit 15 | .done() 16 | .then(() => this.pageInit(this.ss)) 17 | .then(() => this.bindView()); 18 | if (!AppConfig.production) { 19 | window["vm"] = this; 20 | } 21 | } 22 | 23 | initialize() { 24 | return new Promise((rs) => rs()); 25 | } 26 | 27 | bindView() {} 28 | 29 | pageInit(ss) { 30 | return new Promise((resolve) => { 31 | ss.pageInitialized = false; 32 | this.initialize().then(() => { 33 | ss.pageInitialized = true; 34 | // Intro 35 | if (AppConfig.intro.enabled) { 36 | ss.intro.init(); 37 | } 38 | 39 | if (ss.currentRouteParams) { 40 | // pagename 41 | window["pagename"] = ss.currentRouteParams.label; 42 | document.title = ss.currentRouteParams.label; 43 | // breadcrumb 44 | this.state.breadcrumb = ss.currentRouteParams.breadcrumb; 45 | } 46 | resolve(); 47 | }); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/common/services/app.init.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { SharedService } from "./shared.service"; 3 | 4 | @Injectable() 5 | export class AppInitService { 6 | promises: Array> = []; 7 | 8 | constructor(public ss: SharedService) {} 9 | 10 | public init() { 11 | // this.addStep(/** some function */); 12 | } 13 | 14 | public addStep(promise) { 15 | return this.promises.push(promise); 16 | } 17 | 18 | public done() { 19 | return Promise.all(this.promises); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/common/services/cache.service.ts: -------------------------------------------------------------------------------- 1 | export const Cache = window["locache"]; 2 | -------------------------------------------------------------------------------- /src/common/services/hash.service.ts: -------------------------------------------------------------------------------- 1 | import * as jsSHA from "jssha"; 2 | 3 | const defaultHashType = "SHA3-256"; 4 | 5 | export class HashService { 6 | static hash(input: any, length = 32, type = defaultHashType) { 7 | if (input && typeof input === "object") { 8 | input = JSON.stringify(input); 9 | } 10 | 11 | const shaObj = new jsSHA(type, "TEXT"); 12 | shaObj.update(input); 13 | return shaObj.getHash("HEX").substring(0, length); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/common/services/injector.service.ts: -------------------------------------------------------------------------------- 1 | import { Injector } from "@angular/core"; 2 | 3 | export class InjectorService { 4 | static injector: Injector; 5 | } 6 | -------------------------------------------------------------------------------- /src/common/services/shared.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | @Injectable() 4 | export class SharedService { 5 | currentRouteParams: object; 6 | } 7 | -------------------------------------------------------------------------------- /src/common/services/util.service.ts: -------------------------------------------------------------------------------- 1 | import { Cache } from "./cache.service"; 2 | 3 | export const Util = new (class UtilService { 4 | constructor() {} 5 | 6 | truncateCharacter(input, chars, breakOnWord) { 7 | let lastspace; 8 | if (isNaN(chars)) { 9 | return input; 10 | } 11 | if (chars <= 0) { 12 | return ""; 13 | } 14 | if (input && input.length >= chars) { 15 | input = input.substring(0, chars); 16 | if (!breakOnWord) { 17 | lastspace = input.lastIndexOf(" "); 18 | if (lastspace !== -1) { 19 | input = input.substr(0, lastspace); 20 | } 21 | } else { 22 | while (input.charAt(input.length - 1) === " ") { 23 | input = input.substr(0, input.length(-1)); 24 | } 25 | } 26 | return input + "..."; 27 | } 28 | return input; 29 | } 30 | 31 | getUserParam(user) { 32 | return { 33 | nt: user.nt, 34 | fullname: user.displayName, 35 | label: user.label 36 | }; 37 | } 38 | 39 | getWithCache(key: string, isSession: boolean, getFunc, timeout?) { 40 | let cache = Cache; 41 | if (isSession) { 42 | cache = Cache.session; 43 | } 44 | const data = cache.get(key); 45 | 46 | return new Promise((rs, rj) => { 47 | if (data) { 48 | rs(data); 49 | } else { 50 | getFunc() 51 | .then((d) => { 52 | try { 53 | cache.set(key, d, timeout); 54 | } catch (e) { 55 | console.log(e); 56 | } 57 | rs(d); 58 | }) 59 | .catch(rj); 60 | } 61 | }); 62 | } 63 | 64 | solrEscape(q) { 65 | // eslint-disable-next-line no-useless-escape 66 | return q.replace(/[+\-\!\(\)\{\}\[\]\^"~\*\?:\\]+/g, " "); 67 | } 68 | 69 | removeHtmlTags(str) { 70 | return str.replace(/<[^>]*>?/g, ""); 71 | } 72 | 73 | waitUntil(func, check = (d) => !!d, interval = 300, maxTime = 100) { 74 | const doWait = (time) => { 75 | return new Promise((rs, rj) => { 76 | if (time <= 0) { 77 | rj("exceed " + maxTime + " times check"); 78 | } else { 79 | const ret = func(); 80 | if (check(ret)) { 81 | rs(ret); 82 | } else { 83 | setTimeout(() => { 84 | return doWait(time - 1) 85 | .then(rs) 86 | .catch(rj); 87 | }, interval); 88 | } 89 | } 90 | }); 91 | }; 92 | return doWait(maxTime); 93 | } 94 | 95 | deepExtend(...ags: any[]) { 96 | if (ags.length < 1 || typeof ags[0] !== "object") { 97 | return false; 98 | } 99 | if (ags.length < 2) { 100 | return ags[0]; 101 | } 102 | const target = ags[0]; 103 | const args = Array.prototype.slice.call(ags, 1); 104 | args.forEach( 105 | (function(_this) { 106 | return function(obj) { 107 | let clone, key, src, val; 108 | if (typeof obj !== "object") { 109 | return; 110 | } 111 | const _results = []; 112 | for (key in obj) { 113 | if (!(key in obj)) { 114 | continue; 115 | } 116 | src = target[key]; 117 | val = obj[key]; 118 | if (val === target) { 119 | continue; 120 | } 121 | if (typeof val !== "object" || val === null) { 122 | target[key] = val; 123 | continue; 124 | } else if (val instanceof Date) { 125 | target[key] = new Date(val.getTime()); 126 | continue; 127 | } else if (val instanceof RegExp) { 128 | target[key] = new RegExp(val); 129 | continue; 130 | } 131 | if (typeof src !== "object" || src === null) { 132 | clone = Array.isArray(val) ? [] : {}; 133 | target[key] = _this.deepExtend(clone, val); 134 | continue; 135 | } 136 | if (Array.isArray(val)) { 137 | clone = Array.isArray(src) ? src : []; 138 | } else { 139 | clone = !Array.isArray(src) ? src : {}; 140 | } 141 | _results.push((target[key] = _this.deepExtend(clone, val))); 142 | } 143 | return _results; 144 | }; 145 | })(this) 146 | ); 147 | return target; 148 | } 149 | 150 | /* 151 | process exclusive click event and dblclick event 152 | */ 153 | 154 | clicker(clickFunc, dblclickFunc, delay) { 155 | let clicks, timer; 156 | if (delay == null) { 157 | delay = 500; 158 | } 159 | clicks = 0; 160 | timer = null; 161 | return function() { 162 | // eslint-disable-next-line prefer-rest-params 163 | const args = arguments; 164 | clicks += 1; 165 | if (clicks === 1) { 166 | return (timer = setTimeout( 167 | (function(_this) { 168 | return function() { 169 | clicks = 0; 170 | if (this.isFunction(clickFunc)) { 171 | return clickFunc.apply(_this, args); 172 | } 173 | }; 174 | })(this), 175 | delay 176 | )); 177 | } else { 178 | clicks = 0; 179 | clearTimeout(timer); 180 | if (this.isFunction(dblclickFunc)) { 181 | return dblclickFunc.apply(this, args); 182 | } 183 | } 184 | }; 185 | } 186 | 187 | isFunction(functionToCheck) { 188 | return functionToCheck && {}.toString.call(functionToCheck) === "[object Function]"; 189 | } 190 | })(); 191 | -------------------------------------------------------------------------------- /src/config/config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base configuration of application, 3 | * 'config.ENV.ts' will override this configuration 4 | */ 5 | export const Config = { 6 | production: false, 7 | 8 | name: "Harvest", 9 | title: "Harvest", 10 | 11 | uri: { 12 | api: "api" 13 | }, 14 | 15 | default: { 16 | displayCount: 20 17 | }, 18 | 19 | piwik: { 20 | enabled: false, 21 | app: "appName", 22 | prod: "prodName", 23 | url: "", 24 | siteId: 0 25 | }, 26 | 27 | intro: { 28 | enabled: false 29 | }, 30 | 31 | SSO: { 32 | enabled: true, 33 | type: "PFSSO", // read user info from header 34 | // type: 'cookie', // read user info from cookie 35 | 36 | // cookie will still be a fall back of PFSSO when type is `PFSSO` 37 | cookieKeys: ["ihub-user"], // cookies will read 38 | cookieTransform: (values) => { 39 | if (values[0]) { 40 | const objs = JSON.parse(decodeURIComponent(values[0])); 41 | const user = objs.reduce((a, d) => Object.assign(a, d)); 42 | return user; 43 | } 44 | return null; 45 | } 46 | }, 47 | 48 | // sentry support 49 | raven: { 50 | enabled: false, 51 | url: "" 52 | }, 53 | 54 | urlHtml5Mode: true, 55 | version: "1.0.0" 56 | }; 57 | -------------------------------------------------------------------------------- /src/config/config.dev.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The file contents for the current environment 3 | * will overwrite these during build. 4 | */ 5 | 6 | export const Config = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/config.dist.ts: -------------------------------------------------------------------------------- 1 | // `config.dist.ts` is the template for local `config.ts` 2 | 3 | export const Config = { 4 | production: false, 5 | uri: { 6 | api: "http://localhost:1337/" 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/config.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The file contents for the current environment 3 | * will overwrite these during build. 4 | */ 5 | 6 | export const Config = { 7 | production: true, 8 | uri: { 9 | api: "/api" 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * The file contents for the current environment 3 | * will overwrite these during build. 4 | */ 5 | 6 | export const Config = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/constants.ts: -------------------------------------------------------------------------------- 1 | export const AppConstants = { 2 | FONT_STYLE: 3 | "HelveticaNeue-Light,HelveticaNeue-Light,Helvetica Neue Light,Helvetica Neue,Helvetica, Arial,Lucida Grande,sans-serif" 4 | }; 5 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BhavinPatel04/ngx-datetime-range-picker/51ef6a77541925f72acd66d94e5df6cd071e94d0/src/favicon.ico -------------------------------------------------------------------------------- /src/htaccess.dist: -------------------------------------------------------------------------------- 1 | ########################################### 2 | # ======= Enable the Rewrite Engine ======= 3 | 4 | RewriteEngine On 5 | RewriteBase / 6 | 7 | ########################################### 8 | 9 | # Rewrite rule for API proxy 10 | RewriteRule ^api/(.*) http://127.0.0.1:1337/$1 [P,L] 11 | 12 | ########################################### 13 | # ======== SEF URL Routing ======== 14 | 15 | RewriteCond %{REQUEST_FILENAME} !-f 16 | RewriteCond %{REQUEST_FILENAME} !-d 17 | RewriteCond %{REQUEST_URI} !^/.+\.html 18 | RewriteCond %{REQUEST_URI} !^/docs/.*$ 19 | RewriteRule (.*) index.html 20 | 21 | RewriteCond %{REQUEST_FILENAME} !-f 22 | RewriteCond %{REQUEST_FILENAME} !-d 23 | RewriteCond %{REQUEST_URI} ^/docs/.*$ 24 | RewriteCond %{REQUEST_URI} !^/docs/index.html 25 | RewriteCond %{REQUEST_URI} !^/docs/.*\.json 26 | RewriteRule (.*) docs/index.html 27 | 28 | RewriteCond %{REQUEST_URI} ^/docs/.*\.json 29 | RewriteRule ^docs/(.*\.json) $1 30 | 31 | ########################################### 32 | 33 | # ---------------------------------------------------------------------- 34 | # Expires headers (for better cache control) 35 | # ---------------------------------------------------------------------- 36 | 37 | # These are pretty far-future expires headers. 38 | # They assume you control versioning with filename-based cache busting 39 | # Additionally, consider that outdated proxies may miscache 40 | # www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ 41 | 42 | # If you don't use filenames to version, lower the CSS and JS to something like 43 | # "access plus 1 week". 44 | 45 | 46 | ExpiresActive on 47 | 48 | # Perhaps better to whitelist expires rules? Perhaps. 49 | ExpiresDefault "access plus 1 month" 50 | 51 | # cache.appcache needs re-requests in FF 3.6 (thanks Remy ~Introducing HTML5) 52 | ExpiresByType text/cache-manifest "access plus 0 seconds" 53 | 54 | # Your document html 55 | ExpiresByType text/html "access plus 0 seconds" 56 | 57 | # Data 58 | ExpiresByType application/json "access plus 0 seconds" 59 | ExpiresByType application/xml "access plus 0 seconds" 60 | ExpiresByType text/xml "access plus 0 seconds" 61 | ExpiresByType text/plain "access plus 0 seconds" 62 | 63 | # Feed 64 | ExpiresByType application/atom+xml "access plus 1 hour" 65 | ExpiresByType application/rss+xml "access plus 1 hour" 66 | 67 | # Favicon (cannot be renamed) 68 | ExpiresByType image/x-icon "access plus 1 week" 69 | 70 | # Media: images, video, audio 71 | ExpiresByType audio/ogg "access plus 1 month" 72 | ExpiresByType image/gif "access plus 1 month" 73 | ExpiresByType image/jpeg "access plus 1 month" 74 | ExpiresByType image/png "access plus 1 month" 75 | ExpiresByType video/mp4 "access plus 1 month" 76 | ExpiresByType video/ogg "access plus 1 month" 77 | ExpiresByType video/webm "access plus 1 month" 78 | 79 | # HTC files (css3pie) 80 | ExpiresByType text/x-component "access plus 1 month" 81 | 82 | # Webfonts 83 | ExpiresByType application/vnd.ms-fontobject "access plus 1 month" 84 | ExpiresByType application/x-font-ttf "access plus 1 month" 85 | ExpiresByType application/x-font-woff "access plus 1 month" 86 | ExpiresByType font/opentype "access plus 1 month" 87 | ExpiresByType image/svg+xml "access plus 1 month" 88 | 89 | 90 | ########################################### 91 | 92 | ########################################### 93 | # Font 94 | 95 | 96 | Header set Access-Control-Allow-Origin "*" 97 | 98 | 99 | ########################################### 100 | 101 | ########################################### 102 | # Rewrite rule for https 103 | #RewriteCond %{HTTPS} off 104 | #RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} 105 | ########################################### -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NGX-DATETIME-RANGE_PICKER 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: "", 7 | frameworks: ["jasmine", "@angular-devkit/build-angular"], 8 | plugins: [ 9 | require("karma-jasmine"), 10 | require("karma-chrome-launcher"), 11 | require("karma-jasmine-html-reporter"), 12 | require("karma-coverage-istanbul-reporter"), 13 | require("@angular-devkit/build-angular/plugins/karma") 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require("path").join(__dirname, "../coverage"), 20 | reports: ["html", "lcovonly", "text-summary"], 21 | fixWebpackSourcePaths: true, 22 | thresholds: { 23 | statements: 0, 24 | lines: 0, 25 | branches: 0, 26 | functions: 0 27 | } 28 | }, 29 | reporters: ["progress", "kjhtml"], 30 | port: 9876, 31 | colors: true, 32 | logLevel: config.LOG_INFO, 33 | autoWatch: true, 34 | browsers: ["Chrome"], 35 | customLaunchers: { 36 | ChromeHeadlessCI: { 37 | base: "ChromeHeadless", 38 | flags: ["--no-sandbox"] 39 | } 40 | }, 41 | singleRun: true 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from "@angular/core"; 2 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic"; 3 | 4 | import { AppModule } from "./app/app.module"; 5 | import { Config } from "./config/config"; 6 | 7 | import { AppInitService } from "./common/services/app.init.service"; 8 | 9 | if (Config.production) { 10 | enableProdMode(); 11 | } 12 | 13 | platformBrowserDynamic() 14 | .bootstrapModule(AppModule) 15 | .then((module) => { 16 | const injector = module.injector; 17 | const appInit = injector.get(AppInitService); 18 | 19 | // can add any step(promise) here for application initialization 20 | // appInit.addStep(promise) 21 | 22 | appInit.init(); 23 | appInit.done(); 24 | 25 | return injector; 26 | }) 27 | .catch((err) => console.error(err)); 28 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10, IE11, and Chrome <55 requires all of the following polyfills. 22 | * This also includes Android Emulators with older versions of Chrome and Google Search/Googlebot 23 | */ 24 | 25 | // import 'core-js/es6/symbol'; 26 | // import 'core-js/es6/object'; 27 | // import 'core-js/es6/function'; 28 | // import 'core-js/es6/parse-int'; 29 | // import 'core-js/es6/parse-float'; 30 | // import 'core-js/es6/number'; 31 | // import 'core-js/es6/math'; 32 | // import 'core-js/es6/string'; 33 | // import 'core-js/es6/date'; 34 | // import 'core-js/es6/array'; 35 | // import 'core-js/es6/regexp'; 36 | // import 'core-js/es6/map'; 37 | // import 'core-js/es6/weak-map'; 38 | // import 'core-js/es6/set'; 39 | 40 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 41 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 42 | 43 | /** IE10 and IE11 requires the following for the Reflect API. */ 44 | // import 'core-js/es6/reflect'; 45 | 46 | /** 47 | * Web Animations `@angular/platform-browser/animations` 48 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 49 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 50 | */ 51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 52 | 53 | /** 54 | * By default, zone.js will patch all possible macroTask and DomEvents 55 | * user can disable parts of macroTask/DomEvents patch by setting following flags 56 | * because those flags need to be set before `zone.js` being loaded, and webpack 57 | * will put import in the top of bundle, so user need to create a separate file 58 | * in this directory (for example: zone-flags.ts), and put the following flags 59 | * into that file, and then add the following code before importing zone.js. 60 | * import './zone-flags.ts'; 61 | * 62 | * The flags allowed in zone-flags.ts are listed here. 63 | * 64 | * The following flags will work for all browsers. 65 | * 66 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 67 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 68 | * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 69 | * 70 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 71 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 72 | * 73 | * (window as any).__Zone_enable_cross_context_check = true; 74 | * 75 | */ 76 | 77 | /*************************************************************************************************** 78 | * Zone JS is required by default for Angular itself. 79 | */ 80 | import "zone.js"; // Included with Angular CLI. 81 | 82 | /*************************************************************************************************** 83 | * APPLICATION IMPORTS 84 | */ 85 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | html, 4 | body { 5 | height: 100%; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | font-family: Roboto, "Helvetica Neue", sans-serif; 11 | } 12 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import "zone.js/dist/zone-testing"; 4 | import { getTestBed } from "@angular/core/testing"; 5 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from "@angular/platform-browser-dynamic/testing"; 6 | 7 | declare const require: any; 8 | 9 | // First, initialize the Angular testing environment. 10 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting()); 11 | // Then we find all the tests. 12 | const context = require.context("./", true, /\.spec\.ts$/); 13 | // And load the modules. 14 | context.keys().map(context); 15 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": ["main.ts", "polyfills.ts"], 8 | "include": ["src/**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": ["jasmine", "node"] 6 | }, 7 | "files": ["test.ts", "polyfills.ts"], 8 | "include": ["**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "app", "camelCase"], 5 | "component-selector": [true, "element", "app", "kebab-case"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "esModuleInterop": true, 9 | "module": "ESNext", 10 | "moduleResolution": "node", 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "noUnusedLocals": true, 15 | "target": "ESNext", 16 | "typeRoots": ["node_modules/@types"], 17 | "lib": ["es2015", "dom", "es2017", "ESNext"], 18 | "paths": { 19 | "ngx-datetime-range-picker": ["../ngx-datetime-range-picker/dist"], 20 | "ngx-datetime-range-picker/*": ["../ngx-datetime-range-picker/dist/*"] 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "curly": true, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "interface-name": [true, "never-prefix"], 13 | "eofline": true, 14 | "forin": true, 15 | "import-blacklist": [true, "rxjs/Rx"], 16 | "import-spacing": true, 17 | "indent": [true, "spaces"], 18 | "interface-over-type-literal": true, 19 | "label-position": true, 20 | "max-line-length": [true, 140], 21 | "member-access": false, 22 | "member-ordering": [ 23 | true, 24 | { 25 | "order": ["static-field", "instance-field", "static-method", "instance-method"] 26 | } 27 | ], 28 | "no-arg": true, 29 | "no-bitwise": true, 30 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 31 | "no-construct": true, 32 | "no-debugger": true, 33 | "no-duplicate-super": true, 34 | "no-empty": false, 35 | "no-empty-interface": true, 36 | "no-eval": true, 37 | "no-inferrable-types": [true, "ignore-params"], 38 | "no-misused-new": true, 39 | "no-non-null-assertion": true, 40 | "no-redundant-jsdoc": true, 41 | "no-shadowed-variable": true, 42 | "no-string-literal": false, 43 | "no-string-throw": true, 44 | "no-switch-case-fall-through": true, 45 | "no-trailing-whitespace": true, 46 | "no-unnecessary-initializer": true, 47 | "no-unused-expression": true, 48 | "no-var-keyword": true, 49 | "object-literal-sort-keys": false, 50 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], 51 | "prefer-const": true, 52 | "quotemark": [true, "double"], 53 | "radix": true, 54 | "semicolon": [true, "always"], 55 | "triple-equals": [true, "allow-null-check"], 56 | "typedef-whitespace": [ 57 | true, 58 | { 59 | "call-signature": "nospace", 60 | "index-signature": "nospace", 61 | "parameter": "nospace", 62 | "property-declaration": "nospace", 63 | "variable-declaration": "nospace" 64 | } 65 | ], 66 | "unified-signatures": true, 67 | "variable-name": false, 68 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 69 | "no-output-on-prefix": true, 70 | "no-inputs-metadata-property": true, 71 | "no-outputs-metadata-property": true, 72 | "no-host-metadata-property": true, 73 | "no-input-rename": true, 74 | "no-output-rename": true, 75 | "use-lifecycle-interface": true, 76 | "use-pipe-transform-interface": true, 77 | "component-class-suffix": true, 78 | "directive-class-suffix": true 79 | } 80 | } 81 | --------------------------------------------------------------------------------