diff --git a/package.json b/package.json index 998af5cb6df043f09510e61f81481651ac74e041..8dc753fb8ea0f40462e9281a612d18326f33d54b 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "private": true, "dependencies": { + "@datepicker-react/hooks": "^2.8.4", "@popperjs/core": "^2.11.4", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", diff --git a/public/css/main.css b/public/css/main.css index a92f3a475379bc37c0fa3dd3a01c5c890a02f8b5..6e99cb596d0bf730ecd39374c5e12010522463db 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -2756,6 +2756,10 @@ h6 { color: var(--white-70); } +.input-highlight-error-1 { + border-color: red !important; +} + /* .dropdown-toggle::after { margin-left: 1em !important; } */ @@ -2931,7 +2935,7 @@ button.active { } */ .react-confirm-alert-body { - color: var(--white-90) !important; + color: var(--main-black60) !important; background: var(--input-background) !important; border-radius: unset !important; } diff --git a/public/css/primary.css b/public/css/primary.css new file mode 100644 index 0000000000000000000000000000000000000000..ac3c7c714e427a50a70fb796c1bbe9ac26105ca8 --- /dev/null +++ b/public/css/primary.css @@ -0,0 +1,783 @@ +/* Common classes */ +html { + /* Base palette */ + /* Blue Orange */ + --bo-1: #024ba3; + --bo-2: #7b47a4; + --bo-3: #b93f94; + --bo-4: #e24577; + --bo-5: #f56155; + --bo-6: #f58834; + + /* Blue Green */ + --bg-1: #024ba3; + --bg-2: #0068be; + --bg-3: #0082cb; + --bg-4: #019ac9; + --bg-5: #02b1bc; + --bg-6: #00c6a4; + --bg-7: #00d88b; + --bg-8: #8ae770; + + --primary-purple: #d189ff; + --primary-pink: #f3457e; + --full-white: #ffffff; + --full-black: #000000; + + --black-87: rgba(0, 0, 0, 0.87); + --black-60: rgba(0, 0, 0, 0.6); + --black-40: rgba(0, 0, 0, 0.4); + --white-95: rgba(255, 255, 255, 0.95); + --white-70: rgba(255, 255, 255, 0.7); + --white-16: rgba(255, 255, 255, 0.16); + --white-08: rgba(0, 0, 0, 0.08); + --white-02: rgba(255, 255, 255, 0.2); + --light-grey: #f5f5f5; +} + +html[data-theme="dark"] { + /* Base palette */ + /* Blue Orange */ + --bo-1: #024ba3; + --bo-2: #7b47a4; + --bo-3: #b93f94; + --bo-4: #e24577; + --bo-5: #f56155; + --bo-6: #f58834; + + /* Blue Green */ + --bg-1: #024ba3; + --bg-2: #0068be; + --bg-3: #0082cb; + --bg-4: #019ac9; + --bg-5: #02b1bc; + --bg-6: #00c6a4; + --bg-7: #00d88b; + --bg-8: #8ae770; + + --primary-purple: #d189ff; + --primary-pink: #f3457e; + --full-white: #ffffff; + --full-black: #000000; + + --black-87: rgba(0, 0, 0, 0.87); + --black-60: rgba(0, 0, 0, 0.6); + --black-40: rgba(0, 0, 0, 0.4); + --white-95: rgba(255, 255, 255, 0.95); + --white-70: rgba(255, 255, 255, 0.7); + --white-16: rgba(255, 255, 255, 0.16); + --white-08: rgba(0, 0, 0, 0.08); + --white-02: rgba(255, 255, 255, 0.2); + --light-grey: #f5f5f5; +} + +@font-face { + font-family: "Material Icons"; + font-style: normal; + font-weight: 400; + src: url(../fonts/MaterialIcons-Regular.ttf) format("truetype"); +} + +@font-face { + font-family: "Material Icons Round"; + font-style: normal; + font-weight: 400; + src: url(../fonts/MaterialIconsRound-Regular.otf) format("opentype"); +} + +@font-face { + font-family: "Lato-Bold"; + font-weight: 700; + src: local("Lato"), url(../fonts/Lato-Bold.ttf) format("truetype"); +} + +@font-face { + font-family: "Lato-Regular"; + font-weight: 400; + src: local("Lato"), url(../fonts/Lato-Regular.ttf) format("truetype"); +} + +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} + +.material-icons-round { + font-family: "Material Icons Round"; + font-weight: normal; + font-style: normal; + font-size: 18px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} + +.height-min { + min-height: 100vh; +} + +.custom-dropdown-toggle { + background-color: var(--dashboard-selector); + color: var(--white-95); + height: 2.75em; + padding-top: 0.75em; + font-family: "Lato-Regular"; + border-top-right-radius: 24px; + border-bottom-right-radius: 24px; +} + +.right-separator { + border-right: 1px solid var(--black-40); +} + +.vertical-separator { + border-left: 1px solid var(--black-40); + height: 1.65em; + position: absolute; + margin-top: 0.5rem; + margin-right: -0.5rem; + right: 0; + top: 0; +} + +.custom-dropdown-menu { + font-family: "Lato-Regular"; + font-size: 0.875em; + margin-top: 2.8em !important; + transform: none !important; + width: fit-content; +} + +.custom-dropdown-menu a { + text-decoration: none; +} + +.custom-dropdown-menu ul { + color: var(--black-87); + padding-top: 0.8em; + padding-bottom: 0.8em; +} + +.custom-dropdown-menu-sm { + font-family: "Lato-Regular"; + font-size: 0.875em; + margin-left: -0.8% !important; + margin-top: 2.75rem !important; + transform: none !important; + width: max-content; +} + +.custom-dropdown-menu-sm ul { + width: -webkit-fill-available; + color: var(--black-87); + padding-top: 0.8em; + padding-bottom: 0.8em; +} + +.custom-dropdown-menu ul:hover { + background-color: var(--light-grey); +} + +.custom-dropdown-menu-lg { + font-family: "Lato-Regular"; + font-size: 0.875em; + margin-top: 2.2em !important; + transform: none !important; + left: -50vw !important; + width: 50vw; +} + +#dateList { + font-size: 0.8em; +} + +#dateList:hover { + background-color: var(--hover-color); + cursor: pointer; +} + +#dateList { + margin-bottom: 0em; + margin-top: -0.45em; +} + +.active-filter { + background-color: var(--light-grey); +} + +.date-dropdown { + position: absolute; + font-family: "Lato-Regular"; + font-size: 0.875em; + background-color: var(--full-white); + color: var(--widget-text); + border-radius: 0.5em; + width: 85em; + z-index: 1; + height: 25em; + margin-right: 2rem; + right: 1em; + box-shadow: 4px 6px 9px -8px rgba(0, 0, 0, 0.7); +} + +.active-dashboard { + border-left: 2px solid var(--bg-2); + background-color: var(--light-grey); +} + +.chart-widget-box { + background-color: var(--light-grey); + min-height: 4.1875em; + min-width: 5.625em; +} + +.chart-widget-heading { + color: var(--black-60); + font-family: "Lato-Medium"; + font-size: 0.75em; +} + +.dropdown-text-style-1 { + color: var(--black-60); + font-family: "Lato-Bold"; + font-size: 0.875em; +} + +.dropdown-text-style-1::before { + content: "cloud_download"; + font-family: "Material Icons Round"; + font-size: 2em; + padding-right: 0.3em; + vertical-align: -0.28em; + line-height: 0; +} + +/* Style the tab */ +.tab { + overflow: hidden; +} + +/* Style the buttons inside the tab */ + +/* Style the tab content */ + +/* Widget styles ends here */ + +/* Filter styles starts here */ + +.custom-filter { + background-color: var(--secondary-pink); + border-radius: 0.3em; + min-height: 2.5em; +} + +/* Filter styles ends here */ + +/* Toggle button styles starts here */ + +.toggle-button { + width: 16.3125em; + cursor: pointer; + user-select: none; + border: 1.8px solid rgba(0, 0, 0, 0.16); + border-top-left-radius: 1.5em; + border-top-right-radius: 1.5em; + border-bottom-left-radius: 1.5em; + border-bottom-right-radius: 1.5em; + min-height: 2.6em; +} + +.toggle-bg-right { + margin-left: auto; +} + +.toggle-bg-left { + margin-right: auto; +} + +.toggle-trigger-button { + cursor: pointer; + background-color: transparent; + display: flex; + justify-content: center; + align-items: center; + width: fit-content; + min-width: unset; + position: absolute; + left: 6.325em; + transition: all 0.3s ease; + margin-top: -2.15em; +} + +.toggle-background-1 { + background-color: transparent; + left: 1.3em; +} + +.toggle-split-2 { + margin-left: -2.5em !important; + background-color: var(--white-95); + width: 5em; + border-color: var(--white-08); + border-top-right-radius: 1.5em !important; + border-bottom-right-radius: 1.5em !important; +} + +.custom-width-1-left { + width: 8.75rem; + margin-left: -1.9em; + margin-top: -0.125em; +} + +.custom-width-1-right { + width: 4.8125em; + padding: 0.225em 0em 0em 0em; + margin-top: -0.125em; +} + +.custom-width-2 { + width: 12.75em; + margin-top: -0.55rem; + margin-left: -0.5rem; +} + +.breadcrumb { + background-color: transparent; + color: var(--black-60) !important; + font-family: "Lato-Bold" !important; + font-size: 0.875em !important; + cursor: pointer; +} + +.underline-bc:hover { + text-decoration: underline; +} + +.navigate-icon { + line-height: 1; + vertical-align: middle; +} + +/* Toggle button styles ends here */ + +/* Modal styles starts here */ + +#modalView h2 { + font-size: 1em; + color: var(--black-87); +} + +#modalView .material-icons { + font-size: 1.5em; + color: var(--black-87); +} + +.custom-modal { + padding: 2em; + background: var(--full-white); + height: fit-content; + border-radius: 4px; +} + +.afterOpen #root { + opacity: 0.3; +} + +.custom-modal-font-2 { + font-size: 0.975em; + color: var(--black-87); + font-family: "Lato", sans-serif; +} + +.custom-modal-dropdown-1 { + margin-top: 1.325em; + display: none; + position: fixed; + padding: 0.5em 0em 0.5em 0em !important; + background-color: var(--full-white); + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; +} + +.custom-modal-dropdown-1 a { + color: var(--black-87); + font-family: "Lato", sans-serif; +} + +.custom-dd-1:hover .custom-modal-dropdown-1 { + display: block; +} + +.custom-modal-overlay { + position: absolute; + top: 15vh; + left: 20%; + right: 20%; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); + transition: 0.3s; + height: fit-content; + border-radius: 4px; +} + +/* Modal styles ends here */ + +/* iPad */ +@media only screen and (min-width: 768px) and (max-width: 1024px) { + .date-dropdown { + position: absolute; + font-family: "Lato-Regular"; + font-size: 0.875em; + background-color: var(--horizontalbar-color); + color: var(--widget-text); + border-radius: 0.5em; + width: 50em; + z-index: 1; + height: 50em; + margin-right: 2rem; + right: 1em; + box-shadow: 4px 6px 9px -8px rgba(0, 0, 0, 0.7); + } +} + +/* ----------- iPhone 4 and 4S ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat center !important; + background-size: 7em 2.0625em !important; + } + .custom-dropdown-toggle { + height: 2.2em; + } + + .date-dropdown { + position: absolute; + font-family: "Lato-Regular"; + font-size: 0.875em; + background-color: var(--horizontalbar-color); + color: var(--widget-text); + border-radius: 0.5em; + width: fit-content; + z-index: 0; + height: 100%; + margin-right: 0rem; + right: 0em; + box-shadow: 4px 6px 9px -8px rgba(0, 0, 0, 0.7); + } + + .custom-modal-overlay { + left: 0% !important; + right: 0% !important; + } + + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } + + .date-dropdown { + background-color: var(--full-white); + z-index: 1; + overflow: scroll; + } +} + +/* Portrait */ +@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) { + .brand-section { + background: var(--brand-logo) no-repeat center !important; + background-size: 7em 2.0625em !important; + } + .custom-dropdown-toggle { + height: 2.2em; + } +} + +/* Landscape */ +@media only screen and (min-device-width: 320px) and (max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) { + .brand-section { + background: var(--brand-logo) no-repeat center !important; + background-size: 7em 2.0625em !important; + } + .custom-dropdown-toggle { + height: 2.2em; + } +} + +/* ----------- iPhone 5, 5S, 5C and 5SE ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) { + .custom-dropdown-toggle { + height: 2.2em; + } + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } + .date-dropdown { + background-color: var(--full-white); + z-index: 1; + overflow: scroll; + } +} + +/* Portrait */ +@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) { + .custom-dropdown-toggle { + height: auto; + } + .profile-dropdown { + margin: 0.12rem -7rem 0rem !important; + } +} + +/* Landscape */ +@media only screen and (min-device-width: 320px) and (max-device-width: 568px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) { + .custom-dropdown-toggle { + height: 2.2em; + } +} + +/* ----------- iPhone 6, 6S, 7 and 8 ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) { + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } + .date-dropdown { + background-color: var(--full-white); + z-index: 1; + overflow: scroll; + } +} + +/* Portrait */ +@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: portrait) { +} + +/* Landscape */ +@media only screen and (min-device-width: 375px) and (max-device-width: 667px) and (-webkit-min-device-pixel-ratio: 2) and (orientation: landscape) { +} + +/* ----------- iPhone 6+, 7+ and 8+ ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) { + .custom-dropdown-toggle { + height: 2.5em; + } + + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } + .date-dropdown { + background-color: var(--full-white); + z-index: 1; + overflow: scroll; + } +} + +/* Portrait */ +@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait) { + .custom-dropdown-toggle { + height: auto; + } + .profile-dropdown { + margin: 0.125rem -7rem 0rem !important; + } +} + +/* Landscape */ +@media only screen and (min-device-width: 414px) and (max-device-width: 736px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape) { + .custom-dropdown-toggle { + height: 2.5em; + } + + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } +} + +/* ----------- iPhone X ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) { + .login-form { + background-color: var(--primary-bgcolor); + padding-top: 35vh; + } + .date-dropdown { + background-color: var(--full-white); + z-index: 1; + overflow: scroll; + } +} + +/* Portrait */ +@media only screen and (min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: portrait) { +} + +/* Landscape */ +@media only screen and (min-device-width: 375px) and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 3) and (orientation: landscape) { +} + +/* ----------- iPad 1, 2, Mini and Air ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 1) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } + + .custom-modal-overlay { + left: 10% !important; + right: 10% !important; + } + .date-dropdown { + background-color: var(--full-white); + } +} + +/* Portrait */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 1) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* Landscape */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 1) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* ----------- iPad 3, 4 and Pro 9.7" ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 90% 50% !important; + background-size: 5.8em 1.8em !important; + } + .date-dropdown { + background-color: var(--full-white); + } +} + +/* Portrait */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 90% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* Landscape */ +@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 90% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* ----------- iPad Pro 10.5" ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 834px) and (max-device-width: 1112px) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } + .date-dropdown { + background-color: var(--full-white); + } +} + +/* Portrait */ +/* Declare the same value for min- and max-width to avoid colliding with desktops */ +/* Source: https://medium.com/connect-the-dots/css-media-queries-for-ipad-pro-8cad10e17106*/ +@media only screen and (min-device-width: 834px) and (max-device-width: 834px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* Landscape */ +/* Declare the same value for min- and max-width to avoid colliding with desktops */ +/* Source: https://medium.com/connect-the-dots/css-media-queries-for-ipad-pro-8cad10e17106*/ +@media only screen and (min-device-width: 1112px) and (max-device-width: 1112px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* ----------- iPad Pro 12.9" ----------- */ + +/* Portrait and Landscape */ +@media only screen and (min-device-width: 1024px) and (max-device-width: 1366px) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* Portrait */ +/* Declare the same value for min- and max-width to avoid colliding with desktops */ +/* Source: https://medium.com/connect-the-dots/css-media-queries-for-ipad-pro-8cad10e17106*/ +@media only screen and (min-device-width: 1024px) and (max-device-width: 1024px) and (orientation: portrait) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 45% 50% !important; + background-size: 5.8em 1.8em !important; + } +} + +/* Landscape */ +/* Declare the same value for min- and max-width to avoid colliding with desktops */ +/* Source: https://medium.com/connect-the-dots/css-media-queries-for-ipad-pro-8cad10e17106*/ +@media only screen and (min-device-width: 1366px) and (max-device-width: 1366px) and (orientation: landscape) and (-webkit-min-device-pixel-ratio: 2) { + .brand-section { + background: var(--brand-logo) no-repeat !important; + background-position: 80% 50% !important; + background-size: 5.8em 1.8em !important; + } +} diff --git a/public/index.html b/public/index.html index e5fe90f09376d1686de4c0a24238d7acbb4e9b43..d6064cb56d1017a5d6df03f50681262e416419c6 100644 --- a/public/index.html +++ b/public/index.html @@ -31,6 +31,7 @@ <!-- Scripts --> <link rel="stylesheet" href="/css/main.css" /> <link rel="stylesheet" href="/css/new.css" /> + <link rel="stylesheet" href="/css/primary.css" /> <title> SMF </title> diff --git a/src/components/charts/ChartType.js b/src/components/charts/ChartType.js index 8d835884287b05c699eaef4d191c10771510c0b6..24fc83141749b4d013235f1aa74367a701ccdfb4 100644 --- a/src/components/charts/ChartType.js +++ b/src/components/charts/ChartType.js @@ -8,7 +8,6 @@ import _ from "lodash"; import { ChartService } from "../../services"; import ExportChart from "../../helpers/exportChart"; import moment from "moment"; - /** * Component to genearte the required charts * as per the response from the API @@ -26,14 +25,31 @@ class ChartType extends React.Component { this.callAPI(); } + componentDidUpdate(prevProps) { + if ( + prevProps.history.location.state && + prevProps.history.location.state.trigger + ) { + this.callAPI(); + } + } + callAPI() { let code = _.chain(this.props).get("chartData").first().get("id").value(); - let startRange = moment().startOf("year"); - let endRange = moment().endOf("year"); - startRange = Number(startRange); - endRange = Number(endRange); - let thisMonth = { startDate: startRange, endDate: endRange }; + let selectedRange, startRange, endRange; + + if (localStorage.getItem("startDate") && localStorage.getItem("endDate")) { + startRange = moment(localStorage.getItem("startDate")).valueOf(); + endRange = moment(localStorage.getItem("endDate")).valueOf(); + } else { + startRange = moment().startOf("year"); + endRange = moment().endOf("year"); + startRange = Number(startRange); + endRange = Number(endRange); + } + + selectedRange = { startDate: startRange, endDate: endRange }; let payload = { RequestInfo: { @@ -50,7 +66,7 @@ class ChartType extends React.Component { filters: {}, moduleLevel: "", aggregationFactors: null, - requestDate: thisMonth, + requestDate: selectedRange, }, }; diff --git a/src/components/charts/DateFilter.js b/src/components/charts/DateFilter.js new file mode 100644 index 0000000000000000000000000000000000000000..efec542dd6e40e4eb7296d7c0019efa8c991dfa2 --- /dev/null +++ b/src/components/charts/DateFilter.js @@ -0,0 +1,510 @@ +import React, { Component } from "react"; +import * as moment from "moment"; +import { ChartService } from "../../services"; +// import _ from "lodash"; +import DatePicker from "../date-picker/DatePicker"; + +/** + * Custom Date Filter component + */ + +class DateFilter extends Component { + container = React.createRef(); + + constructor(props) { + super(props); + this.state = { + showDateFilter: false, + showCustomDateFilter: false, + showDropDown: false, + dashboardConfigData: [], + selectedDate: moment().format("DD MMM YY"), + selectedFilter: "Today", + rangeSelected: "", + startDate: "", + endDate: "", + trigger: "", + selectedTab: "", + tabsInitDataId: [], + chartsGData: {}, + widgetData: [], + dateListOne: [ + { + date: [moment().startOf("isoWeek"), moment().endOf("isoWeek")], + filter: "This week", + }, + { + date: [moment().startOf("month"), moment().endOf("month")], + filter: "This month", + }, + { + date: [ + moment().quarter(moment().quarter()).startOf("quarter"), + moment().quarter(moment().quarter()).endOf("quarter"), + ], + filter: "This quarter", + }, + { + date: [moment().startOf("year"), moment().endOf("year")], + filter: "This year", + }, + ], + dateListTwo: [ + { + date: [ + moment().subtract(1, "weeks").startOf("isoWeek"), + moment().subtract(1, "weeks").endOf("isoWeek"), + ], + filter: "Last week", + }, + { + date: [ + moment().subtract(1, "month").startOf("month"), + moment().subtract(1, "month").endOf("month"), + ], + filter: "Last month", + }, + { + date: [ + moment() + .quarter(moment().quarter()) + .subtract(1, "quarter") + .startOf("quarter"), + moment() + .quarter(moment().quarter()) + .subtract(1, "quarter") + .endOf("quarter"), + ], + filter: "Last quarter", + }, + { + date: [ + moment().subtract(1, "year").startOf("year"), + moment().subtract(1, "year").endOf("year"), + ], + filter: "Last year", + }, + ], + dateListThree: [ + { + date: [moment().subtract(6, "days"), moment()], + filter: "Last 7 days", + }, + { + date: [moment().subtract(29, "days"), moment()], + filter: "Last 30 days", + }, + { + date: [moment().subtract(3, "month"), moment()], + filter: "Last 3 months", + }, + { + date: [moment().subtract(6, "month"), moment()], + filter: "Last 6 months", + }, + ], + dateColumnOneHeader: [ + { + date: [moment().startOf("day"), +moment().endOf("day")], + filter: "Today", + }, + ], + dateColumnTwoHeader: [ + { + date: [ + moment().subtract(1, "days").startOf("day"), + moment().subtract(1, "days").endOf("day"), + ], + filter: "Yesterday", + }, + ], + showOne: false, + }; + + this.showFilter = this.showFilter.bind(this); + } + + componentDidMount() { + document.addEventListener("mousedown", this.handleClickOutside); + if ( + !localStorage.getItem("selectedFilter") && + !localStorage.getItem("selectedDate") + ) { + let thisMonthRange = + moment().startOf("month").format("DD MMM") + + " - " + + moment().endOf("month").format("DD MMM"); + this.setState({ + selectedFilter: "This month", + selectedDate: thisMonthRange, + }); + } else { + this.setState({ + selectedFilter: localStorage.getItem("selectedFilter"), + selectedDate: localStorage.getItem("selectedDate"), + }); + } + + if (localStorage.getItem("currentDashId")) { + ChartService.getDashboardConfig().then( + (response) => { + this.setState((prevState) => ({ + ...prevState, + dashboardConfigData: response.responseData, + })); + }, + (error) => {} + ); + } else { + setTimeout( + () => + ChartService.getDashboardConfig().then( + (response) => { + this.setState((prevState) => ({ + ...prevState, + dashboardConfigData: response.responseData, + })); + }, + (error) => {} + ), + 1000 + ); + } + } + + componentWillUnmount() { + document.removeEventListener("mousedown", this.handleClickOutside); + } + + UNSAFE_componentWillReceiveProps(nextProps) { + if (nextProps !== undefined) { + if (nextProps.history.location.state !== undefined) { + if (nextProps.history.location.state !== null) { + if (nextProps.history.location.state.trigger === true) { + this.setState({ + showDropDown: false, + }); + if ( + localStorage.getItem("selectedFilter") && + localStorage.getItem("selectedDate") + ) { + this.setState({ + selectedFilter: localStorage.getItem("selectedFilter"), + selectedDate: localStorage.getItem("selectedDate"), + }); + } + } + } else { + this.setState({ + showDropDown: false, + }); + if ( + localStorage.getItem("selectedFilter") && + localStorage.getItem("selectedDate") + ) { + this.setState({ + selectedFilter: localStorage.getItem("selectedFilter"), + selectedDate: localStorage.getItem("selectedDate"), + }); + } + } + } + } + } + + /** + * Function to update the chart visualization + */ + updateVisuals = () => { + this.setState({ + trigger: true, + }); + this.props.history.push({ + pathname: "/analytics", + state: { trigger: this.state.trigger }, + }); + + setTimeout(() => { + this.setState( + { + trigger: false, + }, + () => { + this.props.history.push({ + pathname: "/analytics", + state: { trigger: this.state.trigger }, + }); + } + ); + }, 150); + }; + + /** + * Toggle function to show/hide the custom date filters + */ + showFilter = () => { + this.setState({ + showDateFilter: true, + showDropDown: true, + }); + }; + + /** + * Function to close date dropdown + * when the user clicks outside it + */ + handleClickOutside = (event) => { + if ( + this.container.current && + !this.container.current.contains(event.target) + ) { + this.setState({ + showDateFilter: false, + showDropDown: false, + }); + } + }; + + /** + * Function to get date selected from the custom date filter + */ + async getDate(dateFilter) { + await this.setState( + { + selectedFilter: dateFilter.filter, + selectedDate: + moment(dateFilter.date[0]._d).format("DD MMM") + + " - " + + moment(dateFilter.date[1]._d).format("DD MMM"), + showDateFilter: false, + showCustomDateFilter: false, + startDate: dateFilter.date[0], + endDate: dateFilter.date[1], + }, + () => { + localStorage.setItem("selectedFilter", this.state.selectedFilter); + localStorage.setItem("selectedDate", this.state.selectedDate); + } + ); + await localStorage.setItem("startDate", moment(dateFilter.date[0])); + await localStorage.setItem("endDate", moment(dateFilter.date[1])); + this.updateVisuals(); + } + + /** + * Function to show/hide the date range picker + */ + getCustomDate = () => { + this.setState({ + showCustomDateFilter: true, + }); + }; + + /** + * Function to get the date range + * from the date range picker + */ + onDateChange = (e, l) => { + if (e !== null && l !== null) { + let finalRange = + moment(e).format("DD MMM") + " - " + moment(l).format("DD MMM"); + this.setState( + { + selectedFilter: finalRange, + selectedDate: finalRange, + }, + () => { + localStorage.setItem("selectedFilter", this.state.selectedFilter); + localStorage.setItem("selectedDate", this.state.selectedDate); + } + ); + localStorage.setItem("startDate", moment(e)); + localStorage.setItem("endDate", moment(l)); + if ( + localStorage.getItem("startDate") !== "Invalid date" && + localStorage.getItem("endDate") !== "Invalid date" + ) { + this.setState({ showCustomDateFilter: false, showDateFilter: false }); + this.updateVisuals(); + setTimeout(() => this.updateVisuals(), 1000); + } + } + }; + + render() { + return ( + <div className="mt-3"> + {!this.state.showDateFilter && ( + <div className="btn-group custom-filter-btn"> + <button type="button" className="btn"> + <label + className="custom-filter-label" + style={{ lineHeight: "0" }} + > + Period + </label> + <div className="vertical-separator"></div> + </button> + + <button + type="button" + className="btn dropdown-toggle dropdown-toggle-split" + onClick={this.showFilter} + > + <span className="sr-only">Toggle Dropdown</span> + <span className="custom-filter-selected-value pe-4"> + {this.state.selectedFilter} + </span> + </button> + </div> + )} + {this.state.showDateFilter && ( + <div className="btn-group custom-filter-btn"> + <button type="button" className="btn"> + <label + className="custom-filter-label" + style={{ lineHeight: "0" }} + > + Period + </label> + <div className="vertical-separator"></div> + </button> + + <button + type="button" + className="btn dropdown-toggle dropdown-toggle-split" + data-bs-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + onClick={this.showFilter} + > + <span className="sr-only">Toggle Dropdown</span> + <span className="custom-filter-selected-value pe-4"> + {this.state.selectedFilter} + </span> + </button> + + <div className="dropdown-menu"></div> + </div> + )} + {this.state.showDateFilter && this.state.showDropDown && ( + <div className="container" ref={this.container}> + {!this.state.showCustomDateFilter && ( + <div className="date-dropdown"> + <h6 className="ms-4 font-weight-bold pt-4"> + <b>Presets</b> + </h6> + + <div className="row p-0"> + <div className="col-sm-12 col-md-12 col-lg-12 col-xl-5 col-xxl-5 mb-3"> + <div className="d-md-flex ms-4 mb-1 customMargin mt-2"> + <div className="me-2"> + <div className=""> + {this.state.dateColumnOneHeader.map((dateFilter) => ( + <p + id="dateList" + key={dateFilter.filter} + className={ + this.state.selectedFilter === dateFilter.filter + ? "active-filter p-1" + : "p-1" + } + onClick={() => this.getDate(dateFilter)} + > + {dateFilter.filter} + </p> + ))} + </div> + <div className="customMargin"> + {this.state.dateColumnTwoHeader.map((dateFilter) => ( + <p + id="dateList" + key={dateFilter.filter} + className={ + this.state.selectedFilter === dateFilter.filter + ? "active-filter p-1" + : "p-1" + } + onClick={() => this.getDate(dateFilter)} + > + {dateFilter.filter} + </p> + ))} + </div> + </div> + + <div className="me-2"> + {this.state.dateListOne.map((dateFilter) => ( + <p + id="dateList" + key={dateFilter.filter} + className={ + this.state.selectedFilter === dateFilter.filter + ? "active-filter p-1" + : "p-1" + } + onClick={() => this.getDate(dateFilter)} + > + {dateFilter.filter} + </p> + ))} + </div> + + <div className="customMargin me-2"> + {this.state.dateListTwo.map((dateFilter) => ( + <p + id="dateList" + key={dateFilter.filter} + className={ + this.state.selectedFilter === dateFilter.filter + ? "active-filter p-1" + : "p-1" + } + onClick={() => this.getDate(dateFilter)} + > + {dateFilter.filter} + </p> + ))} + </div> + + <div className="customMargin me-2"> + {this.state.dateListThree.map((dateFilter) => ( + <p + id="dateList" + key={dateFilter.filter} + className={ + this.state.selectedFilter === dateFilter.filter + ? "active-filter p-1" + : "p-1" + } + onClick={() => this.getDate(dateFilter)} + > + {dateFilter.filter} + </p> + ))} + </div> + </div> + </div> + + <div + className="col-sm-12 col-md-12 col-lg-8 col-xl-7 col-xxl-7" + style={{ marginTop: "-1.75rem" }} + > + <div className="me-5"> + <DatePicker + pathName={this.props} + history={this.props.history} + /> + </div> + </div> + </div> + </div> + )} + </div> + )} + </div> + ); + } +} + +export default DateFilter; diff --git a/src/components/charts/GenericCharts.js b/src/components/charts/GenericCharts.js index 9c48961c4c41ed128fe2f3cfe024ccb620f1297d..245561675d00b4c7a29ed8e605b99233ce59702d 100644 --- a/src/components/charts/GenericCharts.js +++ b/src/components/charts/GenericCharts.js @@ -274,6 +274,7 @@ class GenericCharts extends React.Component { section={this.state.modalData.chartDataName} pathName={this.props} dimensions={this.state.modalData.dimensions} + history={this.props.history} /> </div> </Modal> @@ -286,6 +287,7 @@ class GenericCharts extends React.Component { section={chartData.name} pathName={this.props} dimensions={d.dimensions} + history={this.props.history} /> </div> </div> @@ -338,6 +340,7 @@ class GenericCharts extends React.Component { chartData={d.charts} label={d.name} section={chartData.name} + history={this.props.history} /> </div> </div> diff --git a/src/components/charts/LineChart.js b/src/components/charts/LineChart.js index 5f272200328d35badbd2f7d2dfce667a3dfaf866..8f0b7d887765c24bd324286a3f104344097a5972 100644 --- a/src/components/charts/LineChart.js +++ b/src/components/charts/LineChart.js @@ -78,15 +78,15 @@ class LineChart extends React.Component { /** * Function to update the chart visualization */ - updateLineVisuals = () => { - this.setState({ - trigger: true, - }); - this.props.pathName.history.push({ - pathName: "/dashboards", - state: { trigger: this.state.trigger }, - }); - }; + // updateLineVisuals = () => { + // this.setState({ + // trigger: true, + // }); + // this.props.pathName.history.push({ + // pathName: "/dashboards", + // state: { trigger: this.state.trigger }, + // }); + // }; manupulateData(chartData) { var tempdata = { diff --git a/src/components/charts/PieChart.js b/src/components/charts/PieChart.js index 1c75d715be584b219405fc7f6705fd87aaf06800..0d14e1c9a357eb4165f1f4b3e32d55517d2765c7 100644 --- a/src/components/charts/PieChart.js +++ b/src/components/charts/PieChart.js @@ -43,21 +43,21 @@ class PieChart extends React.Component { /** * Function to update the chart visualization */ - updatePieVisuals = () => { - this.setState({ - trigger: true, - }); - this.props.pathName.history.push({ - pathName: "/dashboards", - state: { trigger: this.state.trigger }, - }); - setTimeout(() => { - this.props.pathName.history.push({ - pathName: "/dashboards", - state: { trigger: this.state.trigger }, - }); - }, 500); - }; + // updatePieVisuals = () => { + // this.setState({ + // trigger: true, + // }); + // this.props.pathName.history.push({ + // pathName: "/dashboards", + // state: { trigger: this.state.trigger }, + // }); + // setTimeout(() => { + // this.props.pathName.history.push({ + // pathName: "/dashboards", + // state: { trigger: this.state.trigger }, + // }); + // }, 500); + // }; getData(chartData) { var pieChartTempData = { diff --git a/src/components/charts/WidgetNavBar.js b/src/components/charts/WidgetNavBar.js index 73564f2eaa6d4a78e55f62764c81251f6259dc51..7df832010b6a9982fe37d1dd73e986912c78ae02 100644 --- a/src/components/charts/WidgetNavBar.js +++ b/src/components/charts/WidgetNavBar.js @@ -55,15 +55,32 @@ class WidgetNavBar extends Component { ); } + componentDidUpdate(prevProps) { + if ( + prevProps.history.location.state && + prevProps.history.location.state.trigger + ) { + this.getWidgets(); + } + } + /** * Function to get the chart data as per the dashboard selection */ getChartData = (code) => { - let startRange = moment().startOf("year"); - let endRange = moment().endOf("year"); - startRange = Number(startRange); - endRange = Number(endRange); - let thisMonth = { startDate: startRange, endDate: endRange }; + let selectedRange, startRange, endRange; + + if (localStorage.getItem("startDate") && localStorage.getItem("endDate")) { + startRange = moment(localStorage.getItem("startDate")).valueOf(); + endRange = moment(localStorage.getItem("endDate")).valueOf(); + } else { + startRange = moment().startOf("year"); + endRange = moment().endOf("year"); + startRange = Number(startRange); + endRange = Number(endRange); + } + + selectedRange = { startDate: startRange, endDate: endRange }; let payload = { RequestInfo: { @@ -80,7 +97,7 @@ class WidgetNavBar extends Component { filters: {}, moduleLevel: "", aggregationFactors: null, - requestDate: thisMonth, + requestDate: selectedRange, }, }; @@ -141,25 +158,6 @@ class WidgetNavBar extends Component { ); }; - /** - * Function to update the chart visualization - */ - updateVisuals = () => { - this.setState({ - trigger: true, - }); - this.props.pathName.history.push({ - pathName: "/dashboards", - state: { trigger: this.state.trigger }, - }); - setTimeout(() => { - this.props.pathName.history.push({ - pathName: "/dashboards", - state: { trigger: this.state.trigger }, - }); - }, 500); - }; - render() { return ( <div className="mt-4"> @@ -169,15 +167,18 @@ class WidgetNavBar extends Component { ? "mt-4" : "mt-0" }`} - id="widgets" > {this.state.widgetData.map((data, index) => ( <div - className="col-xs-12 col-sm-12 col-md-4 col-lg-4 col-xl-2 pb-3 pb-xs-3 pb-sm-3 pb-md-3 pb-lg-0 pb-xl-0 widget_card_one px-4" + className={`${ + index > 0 + ? "col-xs-12 col-sm-12 col-md-4 col-lg-4 col-xl-2 pb-3 pb-xs-3 pb-sm-3 pb-md-3 pb-lg-0 pb-xl-0 widget_card_one px-4 ms-0 ms-sm-0 ms-md-3 ms-lg-3 mt-3 mt-sm-3 mt-md-0 mt-lg-0" + : "col-xs-12 col-sm-12 col-md-4 col-lg-4 col-xl-2 pb-3 pb-xs-3 pb-sm-3 pb-md-3 pb-lg-0 pb-xl-0 widget_card_one px-4" + }`} key={data.headerName} > {(data.headerName || data.headerValue) && ( - <div className={`me-2 pt-3 widget-box-${index + 1}`}> + <div className={`me-2 pt-3`}> <h2 className="mt-3"> {!data.isDecimal ? ( <p>{Math.round(data.headerValue)}</p> diff --git a/src/components/date-picker/DatePicker.js b/src/components/date-picker/DatePicker.js new file mode 100644 index 0000000000000000000000000000000000000000..e2fac1d4e0a624ef67ec2e024259adbc7dee1886 --- /dev/null +++ b/src/components/date-picker/DatePicker.js @@ -0,0 +1,111 @@ +import { useState } from "react"; +import { useDatepicker, START_DATE } from "@datepicker-react/hooks"; +import Month from "./Month"; +import NavButton from "./NavButton"; +import DatePickerContext from "./DatePickerContext"; +import * as moment from "moment"; + +function DatePicker(props) { + const [state, setState] = useState({ + startDate: null, + endDate: null, + focusedInput: START_DATE, + }); + + const { + firstDayOfWeek, + activeMonths, + isDateSelected, + isDateHovered, + isFirstOrLastSelectedDate, + isDateBlocked, + isDateFocused, + focusedDate, + onDateHover, + onDateSelect, + onDateFocus, + goToPreviousMonths, + goToNextMonths, + } = useDatepicker({ + startDate: state.startDate, + endDate: state.endDate, + focusedInput: state.focusedInput, + onDatesChange: handleDateChange, + }); + + function handleDateChange(data) { + if (!data.focusedInput) { + setState({ ...data, focusedInput: START_DATE }); + } else { + setState(data); + } + + let sd = data.startDate; + let ed = data.endDate; + if (sd !== null && ed !== null) { + let finalRange = + moment(sd).format("DD MMM") + " - " + moment(ed).format("DD MMM"); + localStorage.setItem("selectedFilter", finalRange); + localStorage.setItem("selectedDate", finalRange); + + setTimeout(() => { + props.history.push({ + pathname: "/analytics", + state: { trigger: true }, + }); + }, 150); + + setTimeout(() => { + props.history.push({ + pathname: "/analytics", + state: { trigger: false }, + }); + }, 200); + localStorage.setItem("startDate", moment(sd)); + localStorage.setItem("endDate", moment(ed)); + } + } + + return ( + <DatePickerContext.Provider + value={{ + focusedDate, + isDateFocused, + isDateSelected, + isDateHovered, + isDateBlocked, + isFirstOrLastSelectedDate, + onDateSelect, + onDateFocus, + onDateHover, + }} + > + <div + className="d-md-flex" + css={{ + display: "grid", + margin: "32px 0 0", + gridTemplateColumns: `repeat(${activeMonths.length}, 300px)`, + gridGap: "0 64px", + }} + > + <NavButton onClick={goToPreviousMonths}> + <span className="material-icons">chevron_left</span> + </NavButton> + {activeMonths.map((month) => ( + <Month + key={`${month.year}-${month.month}`} + year={month.year} + month={month.month} + firstDayOfWeek={firstDayOfWeek} + /> + ))} + <NavButton onClick={goToNextMonths}> + <span className="material-icons">chevron_right</span> + </NavButton> + </div> + </DatePickerContext.Provider> + ); +} + +export default DatePicker; diff --git a/src/components/date-picker/DatePickerContext.js b/src/components/date-picker/DatePickerContext.js new file mode 100644 index 0000000000000000000000000000000000000000..f7125f96ae9ab82004da050c9b4fbd4cb068f83c --- /dev/null +++ b/src/components/date-picker/DatePickerContext.js @@ -0,0 +1,15 @@ +import React from "react"; + +export const datepickerContextDefaultValue = { + focusedDate: null, + isDateFocused: () => false, + isDateSelected: () => false, + isDateHovered: () => false, + isDateBlocked: () => false, + isFirstOrLastSelectedDate: () => false, + onDateFocus: () => {}, + onDateHover: () => {}, + onDateSelect: () => {} +}; + +export default React.createContext(datepickerContextDefaultValue); diff --git a/src/components/date-picker/Day.js b/src/components/date-picker/Day.js new file mode 100644 index 0000000000000000000000000000000000000000..9a9ac5092c84fc90754e5c713c556f6e8cedeec3 --- /dev/null +++ b/src/components/date-picker/Day.js @@ -0,0 +1,111 @@ +import { useRef, useContext } from "react"; +import { useDay } from "@datepicker-react/hooks"; +import DatePickerContext from "./DatePickerContext"; + +function getColor( + isSelected, + isSelectedStartOrEnd, + isWithinHoverRange, + isDisabled +) { + return ({ + selectedFirstOrLastColor, + normalColor, + selectedColor, + rangeHoverColor, + disabledColor + }) => { + if (isSelectedStartOrEnd) { + return selectedFirstOrLastColor; + } else if (isSelected) { + return selectedColor; + } else if (isWithinHoverRange) { + return rangeHoverColor; + } else if (isDisabled) { + return disabledColor; + } else { + return normalColor; + } + }; +} + +function Day({ dayLabel, date }) { + const dayRef = useRef(null); + const { + focusedDate, + isDateFocused, + isDateSelected, + isDateHovered, + isDateBlocked, + isFirstOrLastSelectedDate, + onDateSelect, + onDateFocus, + onDateHover + } = useContext(DatePickerContext); + const { + isSelected, + isSelectedStartOrEnd, + isWithinHoverRange, + disabledDate, + onClick, + onKeyDown, + onMouseEnter, + tabIndex + } = useDay({ + date, + focusedDate, + isDateFocused, + isDateSelected, + isDateHovered, + isDateBlocked, + isFirstOrLastSelectedDate, + onDateFocus, + onDateSelect, + onDateHover, + dayRef + }); + + if (!dayLabel) { + return <div />; + } + + const getColorFn = getColor( + isSelected, + isSelectedStartOrEnd, + isWithinHoverRange, + disabledDate + ); + + return ( + <button + onClick={onClick} + onKeyDown={onKeyDown} + onMouseEnter={onMouseEnter} + tabIndex={tabIndex} + type="button" + ref={dayRef} + style={{ + padding: "8px", + border: 0, + color: getColorFn({ + selectedFirstOrLastColor: "#FFFFFF", + normalColor: "#001217", + selectedColor: "#FFFFFF", + rangeHoverColor: "#FFFFFF", + disabledColor: "#808285" + }), + background: getColorFn({ + selectedFirstOrLastColor: "#00aeef", + normalColor: "#FFFFFF", + selectedColor: "#4448AA", + rangeHoverColor: "#4448AA", + disabledColor: "#FFFFFF" + }) + }} + > + {dayLabel} + </button> + ); +} + +export default Day; diff --git a/src/components/date-picker/GetColor.js b/src/components/date-picker/GetColor.js new file mode 100644 index 0000000000000000000000000000000000000000..4631a22ce0b4d3cfbd3314e7668e291b5db763c4 --- /dev/null +++ b/src/components/date-picker/GetColor.js @@ -0,0 +1,27 @@ +export default function getColor( + isSelected, + isSelectedStartOrEnd, + isWithinHoverRange, + isDisabled + ) { + return ({ + selectedFirstOrLastColor, + normalColor, + selectedColor, + rangeHoverColor, + disabledColor + }) => { + if (isSelectedStartOrEnd) { + return selectedFirstOrLastColor; + } else if (isSelected) { + return selectedColor; + } else if (isWithinHoverRange) { + return rangeHoverColor; + } else if (isDisabled) { + return disabledColor; + } else { + return normalColor; + } + }; + } + \ No newline at end of file diff --git a/src/components/date-picker/Month.js b/src/components/date-picker/Month.js new file mode 100644 index 0000000000000000000000000000000000000000..17d0c7186c920229bf68ee199a31f8fd5198cba7 --- /dev/null +++ b/src/components/date-picker/Month.js @@ -0,0 +1,58 @@ +import { useMonth } from "@datepicker-react/hooks"; +import Day from "./Day"; + +function Month({ year, month, firstDayOfWeek }) { + const { days, weekdayLabels, monthLabel } = useMonth({ + year, + month, + firstDayOfWeek, + }); + + return ( + <div + className="monthCal" + style={{ margin: "2em", backgroundColor: "white" }} + > + <div style={{ textAlign: "center", margin: "0 0 16px" }}> + <strong>{monthLabel}</strong> + </div> + <div + style={{ + display: "grid", + gridTemplateColumns: "repeat(7, 1fr)", + justifyContent: "center", + marginBottom: "10px", + fontSize: "10px", + }} + > + {weekdayLabels.map((dayLabel) => ( + <div style={{ textAlign: "center" }} key={dayLabel}> + {dayLabel} + </div> + ))} + </div> + <div + style={{ + display: "grid", + gridTemplateColumns: "repeat(7, 1fr)", + justifyContent: "center", + }} + > + {days.map((day, index) => { + if (typeof day === "object") { + return ( + <Day + date={day.date} + key={day.date.toString()} + dayLabel={day.dayLabel} + /> + ); + } + return <div key={index} />; + })} + </div> + </div> + ); +} + +export default Month; diff --git a/src/components/date-picker/NavButton.js b/src/components/date-picker/NavButton.js new file mode 100644 index 0000000000000000000000000000000000000000..715badd6be11bc09fc13368c455f54c75c51eb1d --- /dev/null +++ b/src/components/date-picker/NavButton.js @@ -0,0 +1,18 @@ +export default function NavButton({ children, onClick }) { + return ( + <button + type="button" + className="mt-4" + onClick={onClick} + style={{ + border: "1px solid transparent", + padding: "8px", + fontSize: "12em", + height: "5vh", + backgroundColor: "#f8f4f9" + }} + > + {children} + </button> + ); +} diff --git a/src/components/form/AddForm.js b/src/components/form/AddForm.js index 5c3ff351dfe55f3b159bcf419745c224437fbbab..0acb7a0ca21611e58a07864dcdc68c03f43b216d 100644 --- a/src/components/form/AddForm.js +++ b/src/components/form/AddForm.js @@ -38,7 +38,7 @@ class AddForm extends Component { description: "", fields: [], }, - breadcrumbData : [] + breadcrumbData: [], }; } @@ -54,11 +54,11 @@ class AddForm extends Component { })(); this.setState({ breadcrumbData: [ - { title: 'HOME', url: '/dashboard', icon: '' }, - { title: 'MANANGE', url: '/manage', icon: '' }, - { title: 'APPLICATION FORM', url: '', icon: '' }, - ] - }) + { title: "HOME", url: "/dashboard", icon: "" }, + { title: "MANANGE", url: "/manage", icon: "" }, + { title: "APPLICATION FORM", url: "", icon: "" }, + ], + }); if (this.props.match.params.id) { let addFormItem = document.getElementById("active"); if (addFormItem) { @@ -72,9 +72,9 @@ class AddForm extends Component { formTitle: response.responseData.title, breadcrumbData: [ ...this.state.breadcrumbData, - { title: response.responseData.title || '', url: '', icon: '' }, - { title: 'Edit form', url: '', icon: '' }, - ] + { title: response.responseData.title || "", url: "", icon: "" }, + { title: "Edit form", url: "", icon: "" }, + ], }); document.getElementById("id").value = response.responseData.id; document.getElementById("version").value = @@ -104,12 +104,12 @@ class AddForm extends Component { } else { this.setState({ breadcrumbData: [ - { title: 'HOME', url: '/dashboard', icon: '' }, - { title: 'MANANGE', url: '/manage', icon: '' }, - { title: 'APPLICATION FORM', url: '', icon: '' }, - { title: 'CREATE FORM', url: '', icon: '' }, - ] - }) + { title: "HOME", url: "/dashboard", icon: "" }, + { title: "MANANGE", url: "/manage", icon: "" }, + { title: "APPLICATION FORM", url: "", icon: "" }, + { title: "CREATE FORM", url: "", icon: "" }, + ], + }); // this.props.eraseFormDetails(); } } @@ -124,6 +124,15 @@ class AddForm extends Component { }, })); let formName = document.getElementById("form-name"); + if ( + document + .querySelector("#title") + .classList.contains("input-highlight-error-1") + ) { + document + .querySelector("#title") + .classList.remove("input-highlight-error-1"); + } if (event.target.name === "title" && formName) { formName.innerHTML = event.target.value; } @@ -137,10 +146,17 @@ class AddForm extends Component { }; removeElement = (index) => { - // console.log(this.state.formElements) + // console.log(this.state.formElements); + confirmAlert({ - title: LANG.CONFIRM_TO_REMOVE, - message: LANG.ARE_YOU_SURE_YOU_WANT_TO_DO_THIS, + title: + this.state.formElements[index] === "heading" + ? LANG.HEADING_REMOVAL_WARNING + : LANG.CONFIRM_TO_REMOVE, + message: + this.state.formElements[index] === "heading" + ? LANG.CONFIRM_TO_REMOVE_2 + : LANG.ARE_YOU_SURE_YOU_WANT_TO_DO_THIS, buttons: [ { label: LANG.REMOVE, @@ -174,10 +190,16 @@ class AddForm extends Component { formData.version = this.state.formDetails.version; formData.title = this.state.formDetails.title; formData.description = this.state.formDetails.description; - if(isDraft) { - formData.status = LANG.FORM_STATUS.DRAFT + let allowSubmission = false; + + if (document.querySelector("#title").value === "") { + document.querySelector("#title").classList.add("input-highlight-error-1"); + } + + if (isDraft) { + formData.status = LANG.FORM_STATUS.DRAFT; } else { - formData.status = LANG.FORM_STATUS.PUBLISH + formData.status = LANG.FORM_STATUS.PUBLISH; } formData.fields = []; let cards = document.getElementsByClassName("card"); @@ -188,6 +210,13 @@ class AddForm extends Component { field.name = cards[i].querySelector(".fieldName").value; field.fieldType = cards[i].querySelector(".fieldType").value; field.values = []; + + if (field.name === "") { + cards[i] + .querySelector(".fieldName") + .classList.add("input-highlight-error-1"); + } + if ( field.fieldType !== LANG.HEADING || field.fieldType !== LANG.SEPARATOR @@ -223,29 +252,51 @@ class AddForm extends Component { let heading = {}; heading.heading = cards[i].querySelector(".heading").value; heading.subHeading = cards[i].querySelector(".subHeading").value; + + if (heading.heading === "") { + cards[i] + .querySelector(".heading") + .classList.add("input-highlight-error-1"); + allowSubmission = false; + } else { + if ( + cards[i] + .querySelector(".heading") + .classList.contains("input-highlight-error-1") + ) { + cards[i] + .querySelector(".heading") + .classList.remove("input-highlight-error-1"); + } + allowSubmission = true; + } field.values.push(heading); } formData.fields.push(field); } - FormService.add(formData).then( - (response) => { - if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { - Notify.success(response.statusInfo.statusMessage); - // this.props.updateParent(response.responseData.id); - setTimeout(() => { - this.props.history.push("/manage?tab=1"); - }, 500); - } else { - Notify.error(response.statusInfo.errorMessage); + + if (allowSubmission) { + FormService.add(formData).then( + (response) => { + if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { + Notify.success(response.statusInfo.statusMessage); + // this.props.updateParent(response.responseData.id); + setTimeout(() => { + this.props.history.push("/manage?tab=1"); + }, 500); + } else { + Notify.error(response.statusInfo.errorMessage); + } + }, + (error) => { + error.statusInfo + ? Notify.error(error.statusInfo.errorMessage) + : Notify.error(error.message); } - }, - (error) => { - error.statusInfo - ? Notify.error(error.statusInfo.errorMessage) - : Notify.error(error.message); - } - ); - // console.log(formData); + ); + } else { + Notify.error("Kindly fill all the fields"); + } }; render() { @@ -254,7 +305,10 @@ class AddForm extends Component { ); return ( <Fragment> - <Header history={this.props.history} breadCrumb={this.state.breadcrumbData}/> + <Header + history={this.props.history} + breadCrumb={this.state.breadcrumbData} + /> <div className="container-fluid main-container h-100 heightMin"> <div className="container dashboard-inner-container"> <div className="row tabText"> @@ -262,36 +316,39 @@ class AddForm extends Component { <div className="row"> <div className="col-12 mt-5"> <div className="d-flex pull-right"> - <div className="mr-3"> - <BtnOne - label="Cancel" - btnType="button" - isLink={false} - link="" - clickHandler={(e) => this.props.history.push("/manage?tab=1")} - /> - </div> - { this.state.formDetails.status !== LANG.FORM_STATUS.NEW && - <div className="mr-3"> - <BtnOne - label="Save as draft" - btnType="button" - isLink={false} - link="" - clickHandler={(e) => this.submit(true)} - /> - </div> + <div className="mr-3"> + <BtnOne + label="Cancel" + btnType="button" + isLink={false} + link="" + clickHandler={(e) => + this.props.history.push("/manage?tab=1") } - <div className="mr-0"> - <BtnTwo - label="Submit" - btnType="button" - isLink={false} - link="" - clickHandler={(e) => this.submit(false)} - /> - </div> + /> + </div> + {this.state.formDetails.status !== + LANG.FORM_STATUS.NEW && ( + <div className="mr-3"> + <BtnOne + label="Save as draft" + btnType="button" + isLink={false} + link="" + clickHandler={(e) => this.submit(true)} + /> </div> + )} + <div className="mr-0"> + <BtnTwo + label="Submit" + btnType="button" + isLink={false} + link="" + clickHandler={(e) => this.submit(false)} + /> + </div> + </div> </div> </div> <div className="row"> diff --git a/src/components/form/elements/Field.js b/src/components/form/elements/Field.js index 46c46b8ff835ab42819bda5bb9bfb2c18a7a4b14..c7244e9f1dfd941102941f6536d1cc1f15ad1fe4 100644 --- a/src/components/form/elements/Field.js +++ b/src/components/form/elements/Field.js @@ -43,6 +43,10 @@ class Field extends Component { } handleChange = (event) => { + if (event.target.classList.contains("input-highlight-error-1")) { + event.target.classList.remove("input-highlight-error-1") + } + if (event.target.className === "custom-select fieldType input-bg-2") { var value = event.currentTarget.options[event.currentTarget.selectedIndex].text; @@ -111,7 +115,7 @@ class Field extends Component { </select> </div> </div> - <div className="col-md-3" style={{display: "none"}}> + <div className="col-md-3" style={{ display: "none" }}> <div className="form-group"> <label htmlFor="fieldType">Width</label> <select diff --git a/src/components/form/elements/Heading.js b/src/components/form/elements/Heading.js index 4ef20c2ecf1d2aae85c564871907ee4ebcff1e3a..b8fe13ada218e614fb39d75889ea80a3ecd06df6 100644 --- a/src/components/form/elements/Heading.js +++ b/src/components/form/elements/Heading.js @@ -22,6 +22,15 @@ class Heading extends Component { handleChange = (event) => { let field = event.target.name.replace("[]", ""); + if ( + document + .querySelector("[name='heading[]']") + .classList.contains("input-highlight-error-1") + ) { + document + .querySelector("[name='heading[]']") + .classList.remove("input-highlight-error-1"); + } this.setState({ [field]: event.target.value, }); @@ -54,6 +63,7 @@ class Heading extends Component { placeholder="Type here" onChange={this.handleChange} value={this.state.heading || ""} + required /> </div> </div> diff --git a/src/constants/LangConstants.ts b/src/constants/LangConstants.ts index 5d3a4c3470ac4bf327e1a077724de3e13c8dca48..42189d3f6a686baaabcbcbf3adba198c6a188960 100644 --- a/src/constants/LangConstants.ts +++ b/src/constants/LangConstants.ts @@ -52,4 +52,6 @@ export const LANG = { CANCEL: "Cancel", REMOVE: "Remove", CONFIRM_TO_REMOVE: "Confirm to remove", + HEADING_REMOVAL_WARNING: "Are you sure deleting the header?", + CONFIRM_TO_REMOVE_2: "This action will move the question section to the general header category.", }; diff --git a/src/layouts/Dashboard/DashboardLayout.tsx b/src/layouts/Dashboard/DashboardLayout.tsx index cbefac85a582acd5b947a0413a2eaaf42947630d..b882bae50722c74a43f3972716fedbbfbc1bfc22 100644 --- a/src/layouts/Dashboard/DashboardLayout.tsx +++ b/src/layouts/Dashboard/DashboardLayout.tsx @@ -36,6 +36,7 @@ export const DashboardLayout = ({ dashboardConfig }: DashboardLayoutProps) => { chartRowData={k} row={k.row} pathName={history.location.pathname} + history={history} /> ); })} diff --git a/src/layouts/Dashboard/PageLayout.js b/src/layouts/Dashboard/PageLayout.js index 0ece9fd561a9f3b6256882bf3a2491349db7aca1..77fd45c58a2a94613a86e3f20f5290c88bf32db9 100644 --- a/src/layouts/Dashboard/PageLayout.js +++ b/src/layouts/Dashboard/PageLayout.js @@ -24,6 +24,7 @@ class PageLayout extends Component { row={row} chartData={vizData} pathProps={this.props.pathName} + history={this.props.history} /> ))} </div> diff --git a/src/layouts/Inspector/ConsentFormView.tsx b/src/layouts/Inspector/ConsentFormView.tsx index 136b347c8e7995e989e7ae28fbb9b23b98a41ac2..540f07d346f22b53611edc588285ae3829bc63bb 100644 --- a/src/layouts/Inspector/ConsentFormView.tsx +++ b/src/layouts/Inspector/ConsentFormView.tsx @@ -200,13 +200,23 @@ export const ConsentFormView = ({ defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["value"] === "correct" ? true : false, inspectionValue: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["inspectionValue"], - comments: tempArrayTwo[n].fields[k.label]["comments"], - attachments: tempArrayTwo[n].fields[k.label]["attachments"], + comments: + tempArrayTwo && + tempArrayTwo[n].fields.length && + tempArrayTwo[n].fields[k.label]["comments"], + attachments: + tempArrayTwo && + tempArrayTwo[n].fields.length && + tempArrayTwo[n].fields[k.label]["attachments"], }); } }); @@ -856,13 +866,11 @@ export const ConsentFormView = ({ </div> <div className="mt-3"> - <InspectCheckOne label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} showAttachment={false} - attachments={k.attachments} children={ <div className="d-flex flex-row"> diff --git a/src/layouts/Inspector/FormView.tsx b/src/layouts/Inspector/FormView.tsx index 0b176f534e3919e8756333d3167dd41f0998af9a..cf640c56ef5d1ec146807b348ce2f577ceafd198 100644 --- a/src/layouts/Inspector/FormView.tsx +++ b/src/layouts/Inspector/FormView.tsx @@ -192,12 +192,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["value"] === "correct" ? true : false, inspectionValue: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["inspectionValue"], - comments: tempArrayTwo[n].fields[k.label]["comments"], + comments: + tempArrayTwo && + tempArrayTwo[n].fields.length && + tempArrayTwo[n].fields[k.label]["comments"], attachments: [], }); } diff --git a/src/layouts/Regulator/ReviewApplicationLayout.tsx b/src/layouts/Regulator/ReviewApplicationLayout.tsx index 19132c869c4794c7a9ccfa87238979cc9bd0d7c5..d09d74fe913d847288647d382b8d6da3cb2ab4a6 100644 --- a/src/layouts/Regulator/ReviewApplicationLayout.tsx +++ b/src/layouts/Regulator/ReviewApplicationLayout.tsx @@ -189,13 +189,23 @@ export const ReviewApplicationLayout = ({ defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["value"] === "correct" ? true : false, inspectionValue: + tempArrayTwo && + tempArrayTwo[n].fields.length && tempArrayTwo[n].fields[k.label]["inspectionValue"], - comments: tempArrayTwo[n].fields[k.label]["comments"], - attachments: tempArrayTwo[n].fields[k.label]["attachments"], + comments: + tempArrayTwo && + tempArrayTwo[n].fields.length && + tempArrayTwo[n].fields[k.label]["comments"], + attachments: + tempArrayTwo && + tempArrayTwo[n].fields.length && + tempArrayTwo[n].fields[k.label]["attachments"], }); } }); diff --git a/src/pages/Dashboard/Landing.tsx b/src/pages/Dashboard/Landing.tsx index 66fb27f591e654a3922020c7a4e1085e2e99e821..2e260a75376ec454da62749bf2886da094ba532f 100644 --- a/src/pages/Dashboard/Landing.tsx +++ b/src/pages/Dashboard/Landing.tsx @@ -7,6 +7,7 @@ import { DashboardLayout } from "../../layouts"; import { ChartService } from "../../services"; import { APP } from "../../constants"; import Notify from "../../helpers/notify"; +import DateFilter from "../../components/charts/DateFilter"; /** * Landing component renders @@ -21,9 +22,6 @@ export const Landing = ({ data }: LandingProps) => { let history = useHistory(); const [dashboardConfigData, setDashboardConfigData] = useState<any[]>([]); - const [trigger, setTrigger] = useState(false); - const [toggle, setToggle] = useState(false); - const [showFilters, setShowFilters] = useState(false); useEffect(() => { getDashboardConfigurations(); @@ -32,7 +30,7 @@ export const Landing = ({ data }: LandingProps) => { const getDashboardConfigurations = () => { ChartService.getDashboardConfig().then((response) => { if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { - setDashboardConfigData(response.responseData) + setDashboardConfigData(response.responseData); } else { Notify.error(response.statusInfo.errorMessage); } @@ -45,13 +43,25 @@ export const Landing = ({ data }: LandingProps) => { <div className="container-fluid"> <div className="container dashboard-inner-container mt-4"> {/* Section one */} - <section className="pt-3"> - <HeadingOne heading="Insights so far" /> + <section className="row pt-3"> + <div className="col-sm-12 col-md-6 col-lg-6"> + <div className="float-start pt-3"> + <HeadingOne heading="Insights so far" /> + </div> + </div> + <div className="col-sm-12 col-md-6 col-lg-6"> + <div className="float-end"> + <DateFilter + pathName={history.location.pathname} + history={history} + /> + </div> + </div> </section> {/* Dashboards */} <section className=""> - <DashboardLayout dashboardConfig={dashboardConfigData}/> + <DashboardLayout dashboardConfig={dashboardConfigData} /> </section> </div> </div> diff --git a/src/states/atoms.tsx b/src/states/atoms.tsx index 623c12224fdf68a8fd7e3c10e3f3a30b32f41cc5..407c82493c66e5e3573a49fad4ffd75b712f544d 100644 --- a/src/states/atoms.tsx +++ b/src/states/atoms.tsx @@ -1,4 +1,4 @@ -import { atom } from "recoil";; +import { atom } from "recoil"; // Inspector - View applications export const sideMenuData = atom({ @@ -43,4 +43,3 @@ export const dataObjectFileUpload = atom({ key: "dataObjectFileUpload", default: {}, }); - diff --git a/src/styles/chart.css b/src/styles/chart.css index 269f0066cab901b66ed19406db039084a9454ffc..a9b84cfc1d8a131265103f73085f159fce134b37 100644 --- a/src/styles/chart.css +++ b/src/styles/chart.css @@ -17,26 +17,26 @@ } .widget_card_one { - background-color: var(--white-100); - border-radius: 4px; - border: 1px solid var(--black-08); - min-height: 5.563rem; + background-color: var(--white-100) !important; + border-radius: 4px !important; + border: 1px solid var(--black-08) !important; + min-height: 5.563rem !important; } .widget_card_one label { - color: var(--black-87); - font-family: "Lato-Regular"; - font-size: 0.875rem; - letter-spacing: 0.25px; - line-height: 1.5; + color: var(--black-87) !important; + font-family: "Lato-Regular" !important; + font-size: 0.875rem !important; + letter-spacing: 0.25px !important; + line-height: 1.5 !important; } .widget_card_one h2 { - color: var(--black-87); - font-family: "Montserrat-SemiBold"; - font-size: 1.25rem; - letter-spacing: 0.12px; - line-height: 1.4; + color: var(--black-87) !important; + font-family: "Montserrat-SemiBold" !important; + font-size: 1.25rem !important; + letter-spacing: 0.12px !important; + line-height: 1.4 !important; } .custom_cursor { @@ -55,3 +55,25 @@ letter-spacing: 0.25; line-height: 1.429; } + +.custom-filter-btn { + background-color: var(--white-100); + border-radius: 4px; + border: 1px solid var(--black-16); +} + +.custom-filter-label { + color: var(--black-60); + font-family: "Lato-Regular"; + font-size: 0.875rem; + letter-spacing: 0.25; + line-height: 1.429; +} + +.custom-filter-selected-value { + color: var(--black-87); + font-family: "Lato-Bold"; + font-size: 0.875rem; + letter-spacing: 0.25; + line-height: 1.5; +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index f97eb311c7636315f113951dd6e21cc536029b72..6d0454bacfe0facade67ae9fb99eacc770f5c2cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1065,6 +1065,13 @@ resolved "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz" integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg== +"@datepicker-react/hooks@^2.8.4": + version "2.8.4" + resolved "https://registry.yarnpkg.com/@datepicker-react/hooks/-/hooks-2.8.4.tgz#6e07aa98bf21b90b7c88fb35919cca6eb08f2c31" + integrity sha512-qaYJKK5sOSdqcL/OnCtyv3/Q6fRRljfeAyl5ISTPgEO0CM5xZzkGmTx40+6wvqjH5lEZH4ysS95nPyLwZS2tlw== + dependencies: + date-fns "^2.14.0" + "@eslint/eslintrc@^1.0.5": version "1.0.5" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz" @@ -3296,6 +3303,11 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" +date-fns@^2.14.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== + debug@2.6.9, debug@^2.6.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"