diff --git a/package.json b/package.json index b77e4b51f2b910cd329cd298141aca11a0044ce4..998af5cb6df043f09510e61f81481651ac74e041 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "version": "1.0.0", "private": true, "dependencies": { + "@popperjs/core": "^2.11.4", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", "@types/jest": "^27.0.1", + "@types/lodash": "^4.14.118", "@types/node": "^16.7.13", "@types/react": "^17.0.38", "@types/react-dom": "^17.0.11", @@ -14,12 +16,19 @@ "@types/sortablejs": "^1.10.7", "axios": "^0.25.0", "bootstrap": "^5.1.3", + "chart.js": "^3.4.0", + "chartjs-plugin-datalabels": "^2.0.0", + "dom-to-image": "^2.6.0", + "file-saver": "^2.0.5", + "lodash": "^4.17.11", "moment": "^2.29.1", "react": "^17.0.2", + "react-chartjs-2": "^3.0.3", "react-confirm-alert": "^2.7.0", "react-dom": "^17.0.2", "react-loadable": "^5.5.0", "react-localization": "^1.0.17", + "react-modal": "^3.14.4", "react-router-dom": "5.2.0", "react-scripts": "5.0.0", "react-toastify": "^8.1.0", diff --git a/public/css/main.css b/public/css/main.css index 52ca4224ffcfae406e989749125856aa9be067f2..a92f3a475379bc37c0fa3dd3a01c5c890a02f8b5 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -20,11 +20,10 @@ html { --main-black3: rgba(0, 0, 0, 0.3); --main-black1: rgba(0, 0, 0, 0.1); --main-black87: RGBA(0, 0, 0, 0.87); - --main-black40: RGBA(0, 0, 0, 0.40); - --main-black60: rgba(0,0,0,0.60); + --main-black40: RGBA(0, 0, 0, 0.4); + --main-black60: rgba(0, 0, 0, 0.6); --black-16: RGBA(0, 0, 0, 0.16); - /* ticket window */ --ticket-color: #eceff1; @@ -200,21 +199,21 @@ html[data-theme="dark"] { .noselect { -webkit-touch-callout: none; /* iOS Safari */ - -webkit-user-select: none; /* Safari */ - -khtml-user-select: none; /* Konqueror HTML */ - -moz-user-select: none; /* Old versions of Firefox */ - -ms-user-select: none; /* Internet Explorer/Edge */ - user-select: none; /* Non-prefixed version, currently + -webkit-user-select: none; /* Safari */ + -khtml-user-select: none; /* Konqueror HTML */ + -moz-user-select: none; /* Old versions of Firefox */ + -ms-user-select: none; /* Internet Explorer/Edge */ + user-select: none; /* Non-prefixed version, currently supported by Chrome, Edge, Opera and Firefox */ } #root { min-height: 100vh; - background-color: var(--app-background) + background-color: var(--app-background); } .white-bg { - background-color: #fff; + background-color: #fff; } .fullHeight { @@ -537,6 +536,7 @@ hr.topLineHelp { /* iPhone 5 ----------- */ @media only screen and (min-width: 320px) and (max-width: 380px) { /* Styles */ + .customPadding { margin-left: 0em; padding-right: 0em; @@ -872,9 +872,9 @@ hr.topLineHelp { .login-container { background-color: #fff; padding: 4rem 8rem; - font-family: 'Lato'; + font-family: "Lato"; box-shadow: 2px 2px 22px -10px rgb(0 0 0 / 30%); - border-radius: 4px; + border-radius: 4px; } .login-logo { @@ -891,7 +891,7 @@ hr.topLineHelp { } .form-signin .form-control::placeholder { - color: var(--main-black40) + color: var(--main-black40); } .form-signin .form-control { @@ -918,13 +918,13 @@ hr.topLineHelp { margin-top: 1rem; font-size: 0.875rem; margin-bottom: 16px; - font-family: 'Lato-Bold'; + font-family: "Lato-Bold"; font-weight: 500; color: var(--main-black87); } .form-signin .form-text { - font-size: 0.750em; + font-size: 0.75em; margin-bottom: 8px; margin-top: 0; } @@ -1437,7 +1437,7 @@ body { display: none !important; } - .login-container{ + .login-container { padding: 20px 10px; } } @@ -1865,7 +1865,7 @@ textarea, padding: 0.375rem 2rem !important; color: #ffffff !important; margin-top: 16px; - font-family: 'Lato-Bold'; + font-family: "Lato-Bold"; font-weight: 700; } @@ -2306,6 +2306,7 @@ div.tooltip { } .react-confirm-alert-body h1 { + color: #000000; font-size: 1.4em !important; } @@ -2344,6 +2345,11 @@ div.tooltip { margin-right: 3em; } +.invalid-input { + font-size: 0.875rem; + color: red; +} + /* Admin pannel css starts */ .admin-pannel { @@ -2548,7 +2554,7 @@ input[type="checkbox"] { margin-top: -30px; } -.form-check-input[type=checkbox]:focus { +.form-check-input[type="checkbox"]:focus { box-shadow: none !important; } @@ -2564,7 +2570,7 @@ input[type="checkbox"] { position: relative; cursor: pointer; background: #fff; - border: none !important + border: none !important; } input[type="checkbox"]:after { @@ -2590,7 +2596,6 @@ input[type="checkbox"]:after { background: var(--input-background); } - .card-body input[type="checkbox"]:after { border: 1px solid var(--border-1); } @@ -3330,3 +3335,23 @@ html[data-theme="dark"] .admin-left-section ul li.active .profileCircle { } /* Simulator styles ends here */ + +/* User dropdown styles starts here */ + +.username-1 { + font-family: "Lato"; + font-size: 0.875rem; +} + +.email-1 { + font-family: "Lato-Bold"; + font-size: 0.8rem; + color: var(--input-text); +} + +.custom-dropdown-1 { + width: fit-content; + top: 0.5rem !important; +} + +/* User dropdown styles ends here */ diff --git a/src/Router.js b/src/Router.js index 5c8c7f1545aa9266ab6acebca312d3f9908ff108..d3f49600352490827eeee5f9397d98dc102f7b71 100644 --- a/src/Router.js +++ b/src/Router.js @@ -15,7 +15,8 @@ import { ReviewApplication, InspectionSummary, InspectionComplete, - ViewConsentApplications + ViewConsentApplications, + Landing, } from "./pages"; // import ReviewerApplications from "./pages/Reviewer/ReviewerApplications"; import { Manage } from "./pages/Reviewer/manage"; @@ -31,6 +32,7 @@ const Router = (props) => ( <Route exact path="/" component={Login} /> <Route path="/login" component={Login} /> <PrivateRoute path="/dashboard" component={Dashboard} /> + <PrivateRoute path="/analytics" component={Landing} /> <PrivateRoute exact path="/forms" component={ListForms} /> <PrivateRoute exact path="/forms/add" component={AddForm} /> <PrivateRoute exact path="/forms/:id/edit" component={AddForm} /> @@ -52,7 +54,7 @@ const Router = (props) => ( path="/inspector/:id/:applicationId" component={ViewApplications} /> - <PrivateRoute + <PrivateRoute exact path="/assisting-inspector/:id/:applicationId" component={ViewConsentApplications} diff --git a/src/components/buttons/BtnOne.module.css b/src/components/buttons/BtnOne.module.css index 898c171173e09ec2821816e59784dd9ee4ba6885..efabcba5b1b88830a5732b7b135a16863ed30d58 100644 --- a/src/components/buttons/BtnOne.module.css +++ b/src/components/buttons/BtnOne.module.css @@ -16,6 +16,22 @@ width: fit-content; } +.btn_one_disabled { + border: 1.5px solid var(--blue-primary) !important; + border-radius: 4px !important; + background-color: var(--white-100); + color: var(--blue-primary) !important; + font-family: "Lato-Bold" !important; + font-size: 0.875rem !important; + letter-spacing: 0.5px; + line-height: 1.429; + padding: 0.45rem; + padding-left: 0.75rem; + padding-right: 0.75rem; + width: fit-content; + opacity: 0.5 !important; +} + .btn_float_bottom { position: absolute; bottom: 0; diff --git a/src/components/charts/BarChart.js b/src/components/charts/BarChart.js new file mode 100644 index 0000000000000000000000000000000000000000..86859a908c8e8238e3a6e6d736ab4c5e716b8cfd --- /dev/null +++ b/src/components/charts/BarChart.js @@ -0,0 +1,176 @@ +import React from "react"; +import { Bar } from "react-chartjs-2"; +import NFormatterFun from "./NFormatterFun"; +import _ from "lodash"; +import barPalette from "./palette"; + +/** + * BarChart component + */ + +const options = { + scales: { + y: { + grid: { + display: true, + }, + ticks: { + font: { + family: "Lato-Regular", + size: 12, + lineHeight: 1.333, + }, + }, + }, + x: { + grid: { + display: true, + }, + ticks: { + font: { + family: "Lato-Regular", + size: 12, + lineHeight: 1.333, + }, + }, + }, + }, + responsive: true, + options: { + responsive: true, + maintainAspectRatio: true, + }, + plugins: { + legend: { + position: "bottom", + display: true, + labels: { + font: { + family: "Lato-Regular", + size: 14, + lineHeight: 1.429, + }, + }, + }, + datalabels: { + anchor: "end", + align: "end", + }, + }, +}; + +class BarChart extends React.Component { + constructor(props) { + super(props); + this.state = { + trigger: "", + chartsGData: {}, + isData: false, + }; + } + + manupulateData(chartData) { + var barTempData = { + labels: [], + datasets: [], + }; + let colors = barPalette("cb-BasePalette2", chartData.length).map(function ( + hex + ) { + return "#" + hex; + }); + chartData.forEach((d, i) => { + let barTempObj = { + label: "", + borderColor: colors[i], + backgroundColor: colors[i], + fill: false, + }; + let tempdataArr = []; + let tempdatalabel = [], + tempVal = ""; + barTempObj.label = d.headerName; + d.plots.forEach((d1, i) => { + tempVal = NFormatterFun(d1.value, d1.symbol, "Unit"); + tempVal = + typeof tempVal == "string" + ? parseFloat(tempVal.replace(/,/g, "")) + : tempVal; + tempdataArr.push(d1.value); + if (d1.name.length > 20) { + tempdatalabel.push(d1.name.match(/.{1,20}\S+\s*/g)); + } else { + tempdatalabel.push(d1.name); + } + }); + barTempObj.data = tempdataArr; + barTempData.labels = tempdatalabel; + barTempData.datasets.push(barTempObj); + }); + + return barTempData; + } + + contextMenu = (e) => { + e.preventDefault(); + + this.setState({ + isData: false, + }); + }; + + render() { + let { chartData } = this.props; + let { drillDownId } = this.props; + let data; + + /* + * Drilldown chart data + */ + let drillDownData = _.chain(this.state.chartsGData) + .get(drillDownId) + .get("data") + .value(); + + /* + * Condition to load drill down datasets + * if user enabled the drill down feature + */ + if (this.state.isData) { + if (drillDownData !== undefined) { + data = this.manupulateData(drillDownData); + } else { + data = this.manupulateData(chartData); + } + } else { + data = this.manupulateData(chartData); + } + + if (data) { + return ( + <div onContextMenu={this.contextMenu}> + <Bar + height={this.props.dimensions.height} + style={{ fill: "none" }} + data={data} + options={options} + plugins={[ + { + beforeInit: (chart, options) => { + chart.legend.afterFit = () => { + if (chart.legend.margins) { + chart.legend.options.labels.padding = 20; + } + }; + }, + }, + ]} + ></Bar> + </div> + ); + } + return <div>Loading...</div>; + } +} + +export default BarChart; diff --git a/src/components/charts/ChartType.js b/src/components/charts/ChartType.js new file mode 100644 index 0000000000000000000000000000000000000000..8d835884287b05c699eaef4d191c10771510c0b6 --- /dev/null +++ b/src/components/charts/ChartType.js @@ -0,0 +1,187 @@ +import React from "react"; +import LineChart from "./LineChart"; +import BarChart from "./BarChart"; +import PieChart from "./PieChart"; +import MixedData from "./MixedData"; +import MetricVisual from "./MetricVisual"; +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 + */ + +class ChartType extends React.Component { + constructor(props) { + super(props); + this.state = { + chartsGData: {}, + }; + } + + componentDidMount() { + 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 payload = { + RequestInfo: { + authToken: "null", + }, + headers: { + tenantId: "null", + }, + aggregationRequestDto: { + dashboardId: "home", + visualizationType: "METRIC", + visualizationCode: code, + queryType: "", + filters: {}, + moduleLevel: "", + aggregationFactors: null, + requestDate: thisMonth, + }, + }; + + ChartService.getChartData(payload).then( + (response) => { + this.setState((prevState) => ({ + ...prevState, + chartsGData: { + ...prevState.chartsGData, + [code]: response.responseData, + }, + })); + }, + (error) => {} + ); + } + + render() { + let chartKey = _.chain(this.props) + .get("chartData") + .first() + .get("id") + .value(); + + let chartType = _.chain(this.props) + .get("chartData") + .first() + .get("chartType") + .toUpper() + .value(); + + let data = _.chain(this.state) + .get("chartsGData") + .get(chartKey) + .get("data") + .value(); + + let filter = _.chain(this.props) + .get("chartData") + .first() + .get("filter") + .value(); + + let deepFilter = _.chain(this.state) + .get("chartsGData") + .get(chartKey) + .get("filter") + .value(); + + let drillDownId = _.chain(this.state) + .get("chartsGData") + .get(chartKey) + .get("drillDownChartId") + .value(); + + if (filter) { + localStorage.setItem("filterKey", filter); + } else if (deepFilter) { + localStorage.setItem("filterKey", deepFilter); + } + + if (data) { + ExportChart.setAttribute(chartKey, data); + + switch (chartType) { + case "PIE": + return ( + <PieChart + chartData={data} + label={this.props.label} + unit={this.state.unit} + GFilterData={this.props.GFilterData} + dimensions={this.props.dimensions} + section={this.props.section} + pathName={this.props.pathName.pathProps} + /> + ); + case "LINE": + return ( + <LineChart + chartData={data} + label={this.props.label} + unit={this.state.unit} + GFilterData={this.props.GFilterData} + dimensions={this.props.dimensions} + section={this.props.section} + pathName={this.props.pathName.pathProps} + /> + ); + case "BAR": + return ( + <BarChart + chartData={data} + label={this.props.label} + unit={this.state.unit} + GFilterData={this.props.GFilterData} + dimensions={this.props.dimensions} + section={this.props.section} + pathName={this.props.pathName.pathProps} + drillDownId={drillDownId} + filter={filter} + /> + ); + case "LINE_BAR": + return ( + <MixedData + chartData={data} + label={this.props.label} + unit={this.state.unit} + GFilterData={this.props.GFilterData} + dimensions={this.props.dimensions} + section={this.props.section} + /> + ); + case "METRICCOLLECTION": + return ( + <MetricVisual + chartData={data} + label={this.props.label} + unit={this.state.unit} + GFilterData={this.props.GFilterData} + dimensions={this.props.dimensions} + section={this.props.section} + /> + ); + default: + return false; + } + } + return <div> Loading... </div>; + } +} + +export default ChartType; diff --git a/src/components/charts/GenericCharts.js b/src/components/charts/GenericCharts.js new file mode 100644 index 0000000000000000000000000000000000000000..9c48961c4c41ed128fe2f3cfe024ccb620f1297d --- /dev/null +++ b/src/components/charts/GenericCharts.js @@ -0,0 +1,360 @@ +import React from "react"; +import ChartType from "./ChartType"; +import "file-saver"; +import domtoimage from "dom-to-image"; +import Modal from "react-modal"; +import ExportChart from "../../helpers/exportChart"; +import _ from "lodash"; +import "../../styles/chart.css"; + +/** + * GenericChart component to display the + * generated charttypes in the page layout + */ + +Modal.setAppElement("#root"); + +class GenericCharts extends React.Component { + constructor(props) { + super(props); + this.state = { + modalData: { + name: "", + data: [], + chartDataName: "", + dimensions: "", + }, + modalIsOpen: false, + imageBlob: "", + imageBase64: "", + s3URL: "", + telegramURL: "", + chartCode: "", + chartDType: "", + }; + this.getModalData = this.getModalData.bind(this); + this.openModal = this.openModal.bind(this); + this.closeModal = this.closeModal.bind(this); + this.afterOpenModal = this.afterOpenModal.bind(this); + } + + filterImage = (node) => { + return ( + node.id !== "dropdownMenuButton" && + node.id !== "zoomIn" && + node.id !== "zoomOut" && + node.id !== "zoomInBtn" && + node.id !== "zoomOutBtn" && + node.id !== "fullScreenBtn" + ); + }; + + filterImageTwo = (node) => { + return ( + node.id !== "exit" && + node.id !== "downloadAsData" && + node.id !== "downloadAsImage" && + node.id !== "dropdownMenuButtonShare" && + node.id !== "fullScreenBtn" + ); + }; + + openModal = () => { + this.setState({ + modalIsOpen: true, + }); + }; + + afterOpenModal = () => {}; + + closeModal = () => { + this.setState({ + modalIsOpen: false, + }); + }; + + getModalData = (name, description, data, cdName, dimensions) => { + // console.log(name, data, cdName); + this.setState( + { + modalData: { + ...this.state.modalData, + name: name, + description: description, + data: data, + chartDataName: cdName, + dimensions: dimensions, + }, + showModal: true, + chartCode: _.chain(data).first().get("id").value(), + chartDType: _.chain(data).first().get("chartType").toUpper().value(), + }, + () => { + this.openModal(); + } + ); + }; + + renderCharts(d, chartData, index) { + switch (d.vizType.toUpperCase()) { + case "CHART": + return ( + <div + key={index} + className={`col-sm-12 col-md-${d.dimensions.width} col-lg-${d.dimensions.width} mt-2 mb-3`} + > + <div + className="chart-wrapper h-100 chart_card_one chart-wrapper-padding-2" + id={d.name} + > + <div className="mb-3"> + <div className=""> + <div className="row"> + <div className="col-10 col-md-8 col-lg-10"> + <h5 className="pt-1 chart_title_one mb-0">{d.name}</h5> + </div> + <div className="col-2 col-md-4 col-lg-2"> + <div className="d-flex"> + <div + id="fullScreenBtn" + className="material-icons custom_cursor mt-2 black-60" + onClick={() => { + this.getModalData( + d.name, + d.description, + d.charts, + chartData.name, + d.dimensions + ); + }} + > + fullscreen + </div> + <div className="dropdown"> + <div + className="material-icons custom_cursor mt-2 ms-2 black-60" + id="dropdownMenuButton" + data-bs-toggle="dropdown" + aria-expanded="false" + > + more_vert + </div> + <div + className="dropdown-menu dropdown-menu-custom p-0 m-0" + aria-labelledby="dropdownMenuButton" + > + <p + className="dropdown-item custom_cursor dropdown_text mt-3" + onClick={() => + setTimeout(() => { + domtoimage + .toBlob(document.getElementById(d.name), { + filter: this.filterImage, + }) + .then((blob) => + window.saveAs(blob, d.name) + ); + }, 250) + } + > + Download as PNG + </p> + </div> + </div> + </div> + </div> + </div> + + {d.description && ( + <label className="chart_card_description_one"> + {d.description} + </label> + )} + </div> + </div> + + {/* Modal */} + <Modal + id="modalView" + isOpen={this.state.modalIsOpen} + onAfterOpen={this.afterOpenModal} + onRequestClose={this.closeModal} + contentLabel={this.state.modalData.name} + className="custom-modal" + bodyOpenClassName="afterOpen" + overlayClassName="custom-modal-overlay" + > + <div> + <div className="clearfix"> + <div className="float-start"></div> + <div className="float-end custom-modal-font-2"> + <div className="d-flex"> + <div + id="downloadAsImage" + className="custom_cursor " + onClick={() => + domtoimage + .toBlob(document.getElementById("modalView"), { + filter: this.filterImageTwo, + }) + .then((blob) => { + this.setState( + { + imageBlob: blob, + }, + () => { + let reader = new FileReader(); + reader.readAsDataURL(this.state.imageBlob); + + reader.onload = function () { + this.image = reader.result; + }; + window.saveAs( + this.state.imageBlob, + this.state.modalData.name + ); + } + ); + }) + } + > + <span className="float-end me-3"> + Download as image + </span> + <span className="material-icons float-end me-2"> + cloud_download + </span> + </div> + + <div className="custom_cursor " id="downloadAsData"> + <span + className="float-end me-3" + onClick={() => + this.state.chartDType === "TABLE" + ? ExportChart.tableToCsv( + this.state.modalData.name + ) + : ExportChart.toCsv( + this.state.modalData.name, + this.state.chartCode + ) + } + > + Download data + </span> + </div> + <div + className="custom_cursor " + onClick={this.closeModal} + id="exit" + > + <span className="float-end">Exit</span> + <span className="material-icons float-right me-1"> + fullscreen_exit + </span> + </div> + </div> + </div> + </div> + <h2 className="chart-header-style-1"> + {this.state.modalData.name} + </h2> + {this.state.modalData.description && ( + <label className="chart-desc-style-1 pb-3"> + {this.state.modalData.description} + </label> + )} + </div> + + <div className="col-xs-12 col-sm-12 col-md-9 col-lg-8 col-xl-12 centerAlign"> + <ChartType + key={index} + chartData={this.state.modalData.data} + label={this.state.modalData.name} + section={this.state.modalData.chartDataName} + pathName={this.props} + dimensions={this.state.modalData.dimensions} + /> + </div> + </Modal> + + {/* Visualisation */} + <ChartType + key={index} + chartData={d.charts} + label={d.name} + section={chartData.name} + pathName={this.props} + dimensions={d.dimensions} + /> + </div> + </div> + ); + case "METRICCOLLECTION": + return ( + <div + key={index} + className={`col-sm-12 col-md-${d.dimensions.width} col-lg-${d.dimensions.width} mt-2 mb-3`} + > + <div + className="chart-wrapper h-100 cardChart chartWrapperPadding" + id={d.name} + > + <div className="row"> + <h5 className="pb-5 pt-2 pl-3">{d.name}</h5> + <img + className="custom_cursor mt-3 downloadBtn downloadIcon ml-3" + src="data:image/png;base64,R0lGODlhFAAUAIAAAP///wAAACH5BAEAAAAALAAAAAAUABQAAAIRhI+py+0Po5y02ouz3rz7rxUAOw==" + title="Download as PNG" + alt="download chart" + width="13" + height="13" + id="dropdownMenuButton" + data-bs-toggle="dropdown" + ></img> + <div + className="dropdown-menu dropdown-menu-custom" + aria-labelledby="dropdownMenuButton" + style={{ marginLeft: "-11.85em" }} + > + <p + className="dropdown-item custom_cursor metricTextColor" + onClick={() => + setTimeout(() => { + domtoimage + .toBlob(document.getElementById(d.name), { + filter: this.filterImage, + }) + .then((blob) => window.saveAs(blob, d.name)); + }, 250) + } + > + Download as PNG + </p> + </div> + </div> + <ChartType + key={index} + chartData={d.charts} + label={d.name} + section={chartData.name} + /> + </div> + </div> + ); + default: + return <div></div>; + } + } + render() { + let { chartData, row } = this.props; + + return ( + <div key={row} className="row"> + {chartData.vizArray.map((d, i) => this.renderCharts(d, chartData, i))} + </div> + ); + } +} + +export default GenericCharts; diff --git a/src/components/charts/LineChart.js b/src/components/charts/LineChart.js new file mode 100644 index 0000000000000000000000000000000000000000..5f272200328d35badbd2f7d2dfce667a3dfaf866 --- /dev/null +++ b/src/components/charts/LineChart.js @@ -0,0 +1,178 @@ +//Line Chart +import React from "react"; +import { Line } from "react-chartjs-2"; +import NFormatterFun from "./NFormatterFun"; +import linePalette from "./palette"; + +/** + * LineChart component + */ + +const options = { + elements: { + point: { + radius: 0, + }, + line: { + tension: 0, + }, + }, + scales: { + y: { + grid: { + display: true, + }, + ticks: { + font: { + family: "Lato-Regular", + size: 12, + lineHeight: 1.333, + }, + }, + }, + x: { + grid: { + display: true, + }, + ticks: { + font: { + family: "Lato-Regular", + size: 12, + lineHeight: 1.333, + }, + }, + }, + }, + responsive: true, + options: { + responsive: true, + maintainAspectRatio: true, + }, + plugins: { + legend: { + position: "bottom", + display: true, + labels: { + font: { + family: "Lato-Regular", + size: 14, + lineHeight: 1.429, + }, + }, + }, + datalabels: { + anchor: "end", + align: "end", + }, + }, +}; + +class LineChart extends React.Component { + constructor(props) { + super(props); + this.state = { + trigger: "", + }; + } + + /** + * Function to update the chart visualization + */ + updateLineVisuals = () => { + this.setState({ + trigger: true, + }); + this.props.pathName.history.push({ + pathName: "/dashboards", + state: { trigger: this.state.trigger }, + }); + }; + + manupulateData(chartData) { + var tempdata = { + labels: [], + datasets: [], + }; + + chartData.forEach((d, i) => { + let colors = linePalette("cb-BasePalette2", chartData.length).map( + function (hex) { + return "#" + hex; + } + ); + let tempObj = { + label: "", + borderColor: colors[i], + backgroundColor: colors[i], + fill: false, + }; + + let tempdataArr = []; + let tempdatalabel = [], + tempVal = ""; + tempObj.label = d.headerName; + d.plots.forEach((d1, i) => { + tempVal = NFormatterFun(d1.value, d1.symbol, "Unit"); + tempVal = + typeof tempVal == "string" + ? parseFloat(tempVal.replace(/,/g, "")) + : tempVal; + tempdataArr.push(d1.value); + if (d1.name.length > 20) { + tempdatalabel.push(d1.name.match(/.{1,20}\S+\s*/g)); + } else { + tempdatalabel.push(d1.name); + } + }); + tempObj.data = tempdataArr; + tempdata.labels = tempdatalabel; + tempdata.datasets.push(tempObj); + }); + return tempdata; + } + + render() { + let { chartData } = this.props; + let data = this.manupulateData(chartData); + + /* + * Function to get the chart label title + */ + const getLineLabelFilter = (elems) => { + if (localStorage.getItem("filterKey") && elems[0] !== undefined) { + let index = elems[0]._datasetIndex; + let selectedLabel = elems[0]._xScale.chart.data.datasets[index].label; + localStorage.setItem("label", selectedLabel); + this.updateLineVisuals(); + } else { + // console.log("Out!"); + } + }; + + if (data) { + return ( + <Line + height={this.props.dimensions.height} + style={{ fill: "none" }} + data={data} + options={options} + plugins={[ + { + beforeInit: (chart, options) => { + chart.legend.afterFit = () => { + if (chart.legend.margins) { + chart.legend.options.labels.padding = 20; + } + }; + }, + }, + ]} + getElementAtEvent={(elems) => getLineLabelFilter(elems)} + ></Line> + ); + } + return <div>Loading...</div>; + } +} + +export default LineChart; diff --git a/src/components/charts/MetricVisual.js b/src/components/charts/MetricVisual.js new file mode 100644 index 0000000000000000000000000000000000000000..404a19f49fa8da04eb92e014bbf3a9b9d36e06f5 --- /dev/null +++ b/src/components/charts/MetricVisual.js @@ -0,0 +1,37 @@ +import React from "react"; + +/** + * Metric visual component + */ + +class MetricVisual extends React.Component { + getData(chartData) { + return chartData; + } + + render() { + let { chartData } = this.props; + let _data = this.getData(chartData); + + if (_data) { + return ( + <div className=""> + {_data.map((value, index) => ( + <div className="customLineHeight" key={index}> + <p className="metricTextColor">{value.headerName}</p> + {!value.isDecimal ? ( + <p className="largeNum">{Math.round(value.headerValue)}</p> + ) : ( + <p className="largeNum">{value.headerValue}</p> + )} + <p> </p> + </div> + ))} + </div> + ); + } + return <div>Loading...</div>; + } +} + +export default MetricVisual; diff --git a/src/components/charts/MixedData.js b/src/components/charts/MixedData.js new file mode 100644 index 0000000000000000000000000000000000000000..4d3478cd95373005a888600e8eada5712602fd3c --- /dev/null +++ b/src/components/charts/MixedData.js @@ -0,0 +1,148 @@ +import React from "react"; +import { Bar } from "react-chartjs-2"; + +/** + * MixedData component + */ + +const options = { + elements: { + point: { + radius: 0, + }, + line: { + tension: 0, + }, + }, + scales: { + xAxes: [ + { + gridLines: { + color: "rgba(0, 0, 0, 0)", + display: false, + }, + }, + ], + yAxes: [ + { + gridLines: { + display: true, + drawBorder: false, + }, + }, + { + id: "left-y-axis", + display: true, + type: "linear", + position: "left", + }, + { + id: "right-y-axis", + display: true, + type: "linear", + position: "right", + }, + ], + }, + responsive: true, + options: { + responsive: true, + maintainAspectRatio: true, + }, + plugins: { + legend: { + position: "bottom", + }, + }, +}; + +class MixedData extends React.Component { + constructor(props) { + super(props); + this.state = { + data: null, + }; + } + getData(chartData) { + var tempData = { + labels: [], + datasets: [], + }; + var tempdataSet = { + label: "", + type: "line", + borderColor: "#F3457E", + backgroundColor: "transparent", + yAxisID: "right-y-axis", + data: [], + dataSymbol: [], + order: 1, + }; + var tempdataSetTwo = { + label: "", + type: "bar", + backgroundColor: "#7B47A4", + yAxisID: "left-y-axis", + data: [], + dataSymbol: [], + order: 2, + }; + + for (var i = 0; i < chartData.length; i++) { + // Bar Data + if (i === 0) { + tempdataSetTwo.label = chartData[i].headerName; + for (var l = 0; l < chartData[i].plots.length; l++) { + tempData.labels.push(chartData[i].plots[l]["name"]); + tempdataSetTwo.data.push(chartData[i].plots[l]["value"]); + tempdataSetTwo.dataSymbol.push([ + chartData[i].plots[l]["symbol"], + "Unit", + ]); + } + } + // Line Data + if (i === 1) { + tempdataSet.label = chartData[i].headerName; + for (var j = 0; j < chartData[i].plots.length; j++) { + tempdataSet.data.push(chartData[i].plots[j]["value"]); + tempdataSet.dataSymbol.push([ + chartData[i].plots[j]["symbol"], + "Unit", + ]); + } + } + } + + tempData.datasets.push(tempdataSetTwo); + tempData.datasets.push(tempdataSet); + return tempData; + } + + render() { + let { chartData } = this.props; + let _data = this.getData(chartData); + if (_data) { + return ( + <Bar + data={_data} + plugins={[ + { + beforeInit: (chart, options) => { + chart.legend.afterFit = () => { + if (chart.legend.margins) { + chart.legend.options.labels.padding = 20; + } + }; + }, + }, + ]} + options={options} + /> + ); + } + return <div>Loading...</div>; + } +} + +export default MixedData; diff --git a/src/components/charts/NFormatterFun.js b/src/components/charts/NFormatterFun.js new file mode 100644 index 0000000000000000000000000000000000000000..1c168cfed4aa0b989d634fda8064e27fe5624024 --- /dev/null +++ b/src/components/charts/NFormatterFun.js @@ -0,0 +1,79 @@ +export default function NFormatterTest( + value, + type, + symbol, + commaSeparated = false +) { + var SI_SYMBOL = ["Unit", "Lac", "Cr"]; + const Rformatter = new Intl.NumberFormat("en-IN", { + // maximumFractionDigits:0, + useGrouping: true, + // currencyDisplay : Intl.NumberFormatOptions + //style: 'currency', + currency: "INR", + }); + // return value; + switch (type) { + case "amount": + case "Amount": + switch (symbol) { + case SI_SYMBOL[1]: + return ( + `${Rformatter.format((value / 100000).toFixed(2))}` || + `${Rformatter.format(0)}` + ); + + case SI_SYMBOL[2]: + return ( + `${Rformatter.format((value / 10000000).toFixed(2))}` || + `${Rformatter.format(0)}` + ); + + case SI_SYMBOL[0]: + if (value <= 9999999) { + return `${Rformatter.format(value || 0)}`; + } else { + if (!commaSeparated) { + return value; + // let nvalue = Rformatter.format((value).toFixed(2) || 0).replace('₹ ', ''); + // var right = nvalue.substring(nvalue.length - 12, nvalue.length); + // var left = nvalue.substring(0, nvalue.length - 12).replace(',', ''); + // let newVal = (left ? (left + ',') : '') + right; + // return parseFloat(newVal.replace(",,", ',').replace(',', '')); + } else { + let nvalue = Rformatter.format(value.toFixed(2) || 0).replace( + "₹ ", + "" + ); + var right = nvalue.substring(nvalue.length - 12, nvalue.length); + var left = nvalue + .substring(0, nvalue.length - 12) + .replace(",", ""); + let newVal = (left ? left + "," : "") + right; + return newVal.replace(",,", ","); + } + } + + default: + return parseFloat(`${Rformatter.format(value || 0)}`); + } + case "number": + case "Number": + if (!commaSeparated) { + return parseInt(value); + } + const Nformatter = new Intl.NumberFormat("en-IN"); + return Nformatter.format(Math.round(value)); + case "percentage": + case "Percentage": + const Pformatter = new Intl.NumberFormat("en-IN", { + maximumSignificantDigits: 3, + }); + return `${Pformatter.format(value)} %`; + case "text": + case "Text": + return value; + default: + return value; + } +} diff --git a/src/components/charts/PieChart.js b/src/components/charts/PieChart.js new file mode 100644 index 0000000000000000000000000000000000000000..1c75d715be584b219405fc7f6705fd87aaf06800 --- /dev/null +++ b/src/components/charts/PieChart.js @@ -0,0 +1,144 @@ +import React from "react"; +import { Pie } from "react-chartjs-2"; +import _ from "lodash"; +import piePalette from "./palette"; + +/** + * PieChart component + */ + +const pieChartOptions = { + options: { + responsive: true, + maintainAspectRatio: false, + }, + plugins: { + legend: { + position: "bottom", + display: true, + labels: { + font: { + family: "Lato-Regular", + size: 14, + lineHeight: 1.429, + }, + }, + }, + datalabels: { + anchor: "end", + align: "end", + }, + }, +}; + +class PieChart extends React.Component { + constructor(props) { + super(props); + this.state = { + data: null, + trigger: "", + }; + } + + /** + * 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); + }; + + getData(chartData) { + var pieChartTempData = { + labels: [], + datasets: [], + }; + var pieChartTempDataSet = { + label: "", + data: [], + dataSymbol: [], + }; + + _.map(chartData, function (k, v) { + var plots = k["plots"]; + for (var i = 0; i < plots.length; i++) { + pieChartTempData.labels.push(plots[i]["name"]); + pieChartTempDataSet.data.push(plots[i]["value"]); + pieChartTempDataSet.dataSymbol.push([plots[i]["symbol"], "Unit"]); + } + }); + + pieChartTempDataSet.backgroundColor = piePalette( + "cb-BasePalette", + pieChartTempDataSet.data.length + ).map(function (hex) { + return "#" + hex; + }); + pieChartTempDataSet.borderColor = piePalette( + "cb-BasePalette", + pieChartTempDataSet.data.length + ).map(function (hex) { + return "#" + hex; + }); + pieChartTempData.datasets.push(pieChartTempDataSet); + return pieChartTempData; + } + + render() { + let { chartData } = this.props; + let _data = this.getData(chartData); + + /* + * Function to get the chart label title + */ + const getPieLabelFilter = (elems) => { + if (localStorage.getItem("filterKey") && elems[0] !== undefined) { + let index = elems[0]._index; + let selectedLabel = { + labels: [], + }; + selectedLabel.labels.push(elems[0]._chart.data.labels[index]); + localStorage.setItem("label", selectedLabel.labels); + this.updatePieVisuals(); + } else { + // console.log("Out!"); + } + }; + + if (_data) { + return ( + <Pie + height={this.props.dimensions.height} + data={_data} + options={pieChartOptions} + plugins={[ + { + beforeInit: (chart, options) => { + chart.legend.afterFit = () => { + if (chart.legend.margins) { + chart.legend.options.labels.padding = 15; + } + }; + }, + }, + ]} + getElementAtEvent={(elems) => getPieLabelFilter(elems)} + /> + ); + } + return <div>Loading...</div>; + } +} + +export default PieChart; diff --git a/src/components/charts/WidgetNavBar.js b/src/components/charts/WidgetNavBar.js new file mode 100644 index 0000000000000000000000000000000000000000..73564f2eaa6d4a78e55f62764c81251f6259dc51 --- /dev/null +++ b/src/components/charts/WidgetNavBar.js @@ -0,0 +1,199 @@ +import React, { Component } from "react"; +import * as moment from "moment"; +import _ from "lodash"; +import "file-saver"; +// import domtoimage from "dom-to-image"; +import { ChartService } from "../../services"; + +/** + * Widget Navbar Component + * Holds all the widgets and drill throught filter labels + */ + +class WidgetNavBar extends Component { + constructor(props) { + super(props); + this.state = { + showDateFilter: false, + showCustomDateFilter: false, + dashboardConfigData: [], + selectedDate: moment().format("DD MMM YY"), + selectedFilter: "Today", + rangeSelected: "", + startDate: "", + endDate: "", + trigger: "", + selectedTab: "", + tabsInitDataId: [], + chartsGData: {}, + widgetData: [], + showOne: false, + }; + } + + componentDidMount() { + let thisMonthRange = + moment().startOf("month").format("DD MMM") + + " - " + + moment().endOf("month").format("DD MMM"); + this.setState({ + selectedFilter: "This month", + selectedDate: thisMonthRange, + }); + + ChartService.getDashboardConfig().then( + (response) => { + this.setState((prevState) => ({ + ...prevState, + dashboardConfigData: response.responseData, + })); + if (!this.state.chartsGData.length) { + setTimeout(() => this.getWidgets(), 150); + } + }, + (error) => {} + ); + } + + /** + * 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 payload = { + RequestInfo: { + authToken: "null", + }, + headers: { + tenantId: "null", + }, + aggregationRequestDto: { + dashboardId: "home", + visualizationType: "METRIC", + visualizationCode: code, + queryType: "", + filters: {}, + moduleLevel: "", + aggregationFactors: null, + requestDate: thisMonth, + }, + }; + + ChartService.getChartData(payload).then( + (response) => { + this.setState( + (prevState) => ({ + ...prevState, + chartsGData: { + ...prevState.chartsGData, + [code]: response.responseData, + }, + }), + () => { + let chartDetails = JSON.stringify(this.state.chartsGData); + chartDetails = JSON.parse(chartDetails); + chartDetails = _.chain(chartDetails).map(); + chartDetails = JSON.stringify(chartDetails); + chartDetails = JSON.parse(chartDetails); + let chartData = []; + chartDetails.map((details) => chartData.push(details.data[0])); + this.setState({ + widgetData: [...chartData], + }); + } + ); + }, + (error) => {} + ); + }; + + /** + * Function to get the widgets data as per the dashboard selection + */ + getWidgets = () => { + let data = this.state.dashboardConfigData; + let dashboardWidget = _.chain(data) + .first() + .get("widgetCharts") + .groupBy("name") + .value(); + let widgetArray = _.chain(dashboardWidget).map(); + widgetArray = JSON.stringify(widgetArray); + widgetArray = JSON.parse(widgetArray); + let id = []; + widgetArray.map((code) => id.push(code[0].id)); + id.map((code) => this.getChartData(code)); + }; + + filterImage = (node) => { + return ( + node.id !== "downloadDashIcon" && + node.id !== "dropdownMenuButton" && + node.id !== "zoomIn" && + node.id !== "zoomOut" && + node.id !== "zoomInBtn" && + node.id !== "zoomOutBtn" + ); + }; + + /** + * 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"> + <div + className={`row col-12 ${ + this.state.widgetData && this.state.widgetData.length > 0 + ? "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" + key={data.headerName} + > + {(data.headerName || data.headerValue) && ( + <div className={`me-2 pt-3 widget-box-${index + 1}`}> + <h2 className="mt-3"> + {!data.isDecimal ? ( + <p>{Math.round(data.headerValue)}</p> + ) : ( + <p>{data.headerValue}</p> + )} + </h2> + <label className="pb-3">{data.headerName}</label> + </div> + )} + </div> + ))} + </div> + </div> + ); + } +} + +export default WidgetNavBar; diff --git a/src/components/charts/palette.js b/src/components/charts/palette.js new file mode 100644 index 0000000000000000000000000000000000000000..a3a8659f006edee0b77d753fd47aac84c167e60d --- /dev/null +++ b/src/components/charts/palette.js @@ -0,0 +1,1568 @@ +/* eslint-disable no-unused-expressions */ +/** @license + * + * Colour Palette Generator script. + * Copyright (c) 2014 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + * + * Furthermore, ColorBrewer colour schemes are covered by the following: + * + * Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and + * The Pennsylvania State University. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain + * a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * 1. Redistributions as source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. The end-user documentation included with the redistribution, if any, + * must include the following acknowledgment: "This product includes color + * specifications and designs developed by Cynthia Brewer + * (http://colorbrewer.org/)." Alternately, this acknowledgment may appear + * in the software itself, if and wherever such third-party + * acknowledgments normally appear. + * + * 4. The name "ColorBrewer" must not be used to endorse or promote products + * derived from this software without prior written permission. For written + * permission, please contact Cynthia Brewer at cbrewer@psu.edu. + * + * 5. Products derived from this software may not be called "ColorBrewer", + * nor may "ColorBrewer" appear in their name, without prior written + * permission of Cynthia Brewer. + * + * Furthermore, Solarized colour schemes are covered by the following: + * + * Copyright (c) 2011 Ethan Schoonover + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// 'use strict'; + + var palette = (function() { + + var proto = Array.prototype; + var slice = function(arr, opt_begin, opt_end) { + return proto.slice.apply(arr, proto.slice.call(arguments, 1)); + }; + + // var extend = function(arr, arr2) { + // return proto.push.apply(arr, arr2); + // }; + + var function_type = typeof function() {}; + + var INF = 1000000000; // As far as we're concerned, that's infinity. ;) + + + /** + * Generate a colour palette from given scheme. + * + * If scheme argument is not a function it is passed to palettes.listSchemes + * function (along with the number argument). This may result in an array + * of more than one available scheme. If that is the case, scheme at + * opt_index position is taken. + * + * This allows using different palettes for different data without having to + * name the schemes specifically, for example: + * + * palette_for_foo = palette('sequential', 10, 0); + * palette_for_bar = palette('sequential', 10, 1); + * palette_for_baz = palette('sequential', 10, 2); + * + * @param {!palette.SchemeType|string|palette.Palette} scheme Scheme to + * generate palette for. Either a function constructed with + * palette.Scheme object, or anything that palette.listSchemes accepts + * as name argument. + * @param {number} number Number of colours to return. If negative, absolute + * value is taken and colours will be returned in reverse order. + * @param {number=} opt_index If scheme is a name of a group or an array and + * results in more than one scheme, index of the scheme to use. The + * index wraps around. + * @param {...*} varargs Additional arguments to pass to palette or colour + * generator (if the chosen scheme uses those). + * @return {Array<string>} Array of abs(number) 'RRGGBB' strings or null if + * no matching scheme was found. + */ + var palette = function(scheme, number, opt_index, varargs) { + number |= 0; + if (number === 0) { + return []; + } + + if (typeof scheme !== function_type) { + var arr = palette.listSchemes( + /** @type {string|palette.Palette} */ (scheme), number); + if (!arr.length) { + return null; + } + scheme = arr[(opt_index || 0) % arr.length]; + } + + var args = slice(arguments, 2); + args[0] = number; + return scheme.apply(scheme, args); + }; + + + /** + * Returns a callable colour scheme object. + * + * Just after being created, the scheme has no colour palettes and no way of + * generating any, thus generate method will return null. To turn scheme + * into a useful object, addPalette, addPalettes or setColorFunction methods + * need to be used. + * + * To generate a colour palette with given number colours using function + * returned by this method, just call it with desired number of colours. + * + * Since this function *returns* a callable object, it must *not* be used + * with the new operator. + * + * @param {string} name Name of the scheme. + * @param {string|!Array<string>=} opt_groups A group name or list of + * groups the scheme should be categorised under. Three typical groups + * to use are 'qualitative', 'sequential' and 'diverging', but any + * groups may be created. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme = function(name, opt_groups) { + /** + * A map from a number to a colour palettes with given number of colours. + * @type {!Object<number, palette.Palette>} + */ + var palettes = {}; + + /** + * The biggest palette in palettes map. + * @type {number} + */ + var palettes_max = 0; + + /** + * The smallest palette in palettes map. + * @type {number} + */ + var palettes_min = INF; + + var makeGenerator = function() { + if (arguments.length <= 1) { + return self.color_func.bind(self); + } else { + var args = slice(arguments); + return function(x) { + args[0] = x; + return self.color_func.apply(self, args); + }; + } + }; + + /** + * Generate a colour palette from the scheme. + * + * If there was a palette added with addPalette (or addPalettes) with + * enough colours, that palette will be used. Otherwise, if colour + * function has been set using setColorFunction method, that function will + * be used to generate the palette. Otherwise null is returned. + * + * @param {number} number Number of colours to return. If negative, + * absolute value is taken and colours will be returned in reverse + * order. + * @param {...*} varargs Additional arguments to pass to palette or colour + * generator (if the chosen scheme uses those). + */ + var self = function(number, varargs) { + number |= 0; + if (!number) { + return []; + } + + var _number = number; + number = Math.abs(number); + + if (number <= palettes_max) { + for (var i = Math.max(number, palettes_min); !(i in palettes); ++i) { + /* nop */ + } + var colors = palettes[i]; + if (i > number) { + var take_head = + 'shrinking_takes_head' in colors ? + colors.shrinking_takes_head : self.shrinking_takes_head; + if (take_head) { + colors = colors.slice(0, number); + i = number; + } else { + return palette.generate( + function(x) { return colors[Math.round(x)]; }, + _number, 0, colors.length - 1); + } + } + colors = colors.slice(); + if (_number < 0) { + colors.reverse(); + } + return colors; + + } else if (self.color_func) { + return palette.generate(makeGenerator.apply(self, arguments), + _number, 0, 1, self.color_func_cyclic); + + } else { + return null; + } + }; + + /** + * The name of the palette. + * @type {string} + */ + self.scheme_name = name; + + /** + * A list of groups the palette belongs to. + * @type {!Array<string>} + */ + self.groups = opt_groups ? + typeof opt_groups === 'string' ? [opt_groups] : opt_groups : []; + + /** + * The biggest palette this scheme can generate. + * @type {number} + */ + self.max = 0; + + /** + * The biggest palette this scheme can generate that is colour-blind + * friendly. + * @type {number} + */ + self.cbf_max = INF; + + + /** + * Adds a colour palette to the colour scheme. + * + * @param {palette.Palette} palette An array of 'RRGGBB' strings + * representing the palette to add. + * @param {boolean=} opt_is_cbf Whether the palette is colourblind friendly. + */ + self.addPalette = function(palette, opt_is_cbf) { + var len = palette.length; + if (len) { + palettes[len] = palette; + palettes_min = Math.min(palettes_min, len); + palettes_max = Math.max(palettes_max, len); + self.max = Math.max(self.max, len); + if (!opt_is_cbf && len !== 1) { + self.cbf_max = Math.min(self.cbf_max, len - 1); + } + } + }; + + /** + * Adds number of colour palettes to the colour scheme. + * + * @param {palette.PalettesList} palettes A map or an array of colour + * palettes to add. If map, i.e. object, is used, properties should + * use integer property names. + * @param {number=} opt_max Size of the biggest palette in palettes set. + * If not set, palettes must have a length property which will be used. + * @param {number=} opt_cbf_max Size of the biggest palette which is still + * colourblind friendly. 1 by default. + */ + self.addPalettes = function(palettes, opt_max, opt_cbf_max) { + opt_max = opt_max || palettes.length; + for (var i = 0; i < opt_max; ++i) { + if (i in palettes) { + self.addPalette(palettes[i], true); + } + } + self.cbf_max = Math.min(self.cbf_max, opt_cbf_max || 1); + }; + + /** + * Enable shrinking palettes taking head of the list of colours. + * + * When user requests n-colour palette but the smallest palette added with + * addPalette (or addPalettes) is m-colour one (where n < m), n colours + * across the palette will be returned. For example: + * var ex = palette.Scheme('ex'); + * ex.addPalette(['000000', 'bcbcbc', 'ffffff']); + * var pal = ex(2); + * // pal == ['000000', 'ffffff'] + * + * This works for palettes where the distance between colours is + * correlated to distance in the palette array, which is true in gradients + * such as the one above. + * + * To turn this feature off shrinkByTakingHead can be set to true either + * for all palettes in the scheme (if opt_idx is not given) or for palette + * with given number of colours only. In general, setting the option for + * given palette overwrites whatever has been set for the scheme. The + * default, as described above, is false. + * + * Alternatively, the feature can be enabled by setting shrinking_takes_head + * property for the palette Array or the scheme object. + * + * For example, all of the below give equivalent results: + * var pal = ['ff0000', '00ff00', '0000ff']; + * + * var ex = palette.Scheme('ex'); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * ex.shrinkByTakingHead(true); // ex(2) == ['ff0000', '00ff00'] + * + * ex = palette.Scheme('ex'); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * ex.shrinkByTakingHead(true, 3); // ex(2) == ['ff0000', '00ff00'] + * + * ex = palette.Scheme('ex'); + * ex.addPalette(pal); + * ex.addPalette(pal); // ex(2) == ['ff0000', '0000ff'] + * pal.shrinking_takes_head = true; // ex(2) == ['ff0000', '00ff00'] + * + * @param {boolean} enabled Whether to enable or disable the “shrinking + * takes head†feature. It is disabled by default. + * @param {number=} opt_idx If given, the “shrinking takes head†option + * for palette with given number of colours is set. If such palette + * does not exist, nothing happens. + */ + self.shrinkByTakingHead = function(enabled, opt_idx) { + if (opt_idx !== void(0)) { + if (opt_idx in palettes) { + palettes[opt_idx].shrinking_takes_head = !!enabled; + } + } else { + self.shrinking_takes_head = !!enabled; + } + }; + + /** + * Sets a colour generation function of the colour scheme. + * + * The function must accept a singe number argument whose value can be from + * 0.0 to 1.0, and return a colour as an 'RRGGBB' string. This function + * will be used when generating palettes, i.e. if 11-colour palette is + * requested, this function will be called with arguments 0.0, 0.1, …, 1.0. + * + * If the palette generated by the function is colourblind friendly, + * opt_is_cbf should be set to true. + * + * In some cases, it is not desirable to reach 1.0 when generating + * a palette. This happens for hue-rainbows where the 0–1 range corresponds + * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°, + * it's desired to stop short the end of the range when generating + * a palette. To accomplish this, opt_cyclic should be set to true. + * + * @param {palette.ColorFunction} func A colour generator function. + * @param {boolean=} opt_is_cbf Whether palette generate with the function + * is colour-blind friendly. + * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the + * one at 1.0. + */ + self.setColorFunction = function(func, opt_is_cbf, opt_cyclic) { + self.color_func = func; + self.color_func_cyclic = !!opt_cyclic; + self.max = INF; + if (!opt_is_cbf && self.cbf_max === INF) { + self.cbf_max = 1; + } + }; + + self.color = function(x, varargs) { + if (self.color_func) { + return self.color_func.apply(this, arguments); + } else { + return null; + } + }; + + return self; + }; + + + /** + * Creates a new palette.Scheme and initialises it by calling addPalettes + * method with the rest of the arguments. + * + * @param {string} name Name of the scheme. + * @param {string|!Array<string>} groups A group name or list of group + * names the scheme belongs to. + * @param {!Object<number, palette.Palette>|!Array<palette.Palette>} + * palettes A map or an array of colour palettes to add. If map, i.e. + * object, is used, properties should use integer property names. + * @param {number=} opt_max Size of the biggest palette in palettes set. + * If not set, palettes must have a length property which will be used. + * @param {number=} opt_cbf_max Size of the biggest palette which is still + * colourblind friendly. 1 by default. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme.fromPalettes = function(name, groups, + palettes, opt_max, opt_cbf_max) { + var scheme = palette.Scheme(name, groups); + scheme.addPalettes.apply(scheme, slice(arguments, 2)); + return scheme; + }; + + + /** + * Creates a new palette.Scheme and initialises it by calling + * setColorFunction method with the rest of the arguments. + * + * @param {string} name Name of the scheme. + * @param {string|!Array<string>} groups A group name or list of group + * names the scheme belongs to. + * @param {palette.ColorFunction} func A colour generator function. + * @param {boolean=} opt_is_cbf Whether palette generate with the function + * is colour-blind friendly. + * @param {boolean=} opt_cyclic Whether colour at 0.0 is the same as the + * one at 1.0. + * @return {!palette.SchemeType} A colour palette generator function, which + * in addition has methods and properties like a regular object. Think + * of it as a callable object. + */ + palette.Scheme.withColorFunction = function(name, groups, + func, opt_is_cbf, opt_cyclic) { + var scheme = palette.Scheme(name, groups); + scheme.setColorFunction.apply(scheme, slice(arguments, 2)); + return scheme; + }; + + + /** + * A map of registered schemes. Maps a scheme or group name to a list of + * scheme objects. Property name is either 'n-<name>' for single scheme + * names or 'g-<name>' for scheme group names. + * + * @type {!Object<string, !Array<!Object>>} + */ + var registered_schemes = {}; + + + /** + * Registers a new colour scheme. + * + * @param {!palette.SchemeType} scheme The scheme to add. + */ + palette.register = function(scheme) { + registered_schemes['n-' + scheme.scheme_name] = [scheme]; + scheme.groups.forEach(function(g) { + (registered_schemes['g-' + g] = + registered_schemes['g-' + g] || []).push(scheme); + }); + (registered_schemes['g-all'] = + registered_schemes['g-all'] || []).push(scheme); + }; + + + /** + * List all schemes that match given name and number of colours. + * + * name argument can be either a string or an array of strings. In the + * former case, the function acts as if the argument was an array with name + * as a single argument (i.e. “palette.listSchemes('foo')†is exactly the same + * as “palette.listSchemes(['foo'])â€). + * + * Each name can be either name of a palette (e.g. 'tol-sq' for Paul Tol's + * sequential palette), or a name of a group (e.g. 'sequential' for all + * sequential palettes). Name can therefore map to a single scheme or + * several schemes. + * + * Furthermore, name can be suffixed with '-cbf' to indicate that only + * schemes that are colourblind friendly should be returned. For example, + * 'rainbow' returns a HSV rainbow scheme, but because it is not colourblind + * friendly, 'rainbow-cbf' returns no schemes. + * + * Some schemes may produce colourblind friendly palettes for some number of + * colours. For example ColorBrewer's Dark2 scheme is colourblind friendly + * if no more than 3 colours are generated. If opt_number is not specified, + * 'qualitative-cbf' will include 'cb-Dark2' but if opt_number is given as, + * say, 5 it won't. + * + * Name can also be 'all' which will return all registered schemes. + * Naturally, 'all-cbf' will return all colourblind friendly schemes. + * + * Schemes are added to the library using palette.register. Schemes are + * created using palette.Scheme function. By default, the following schemes + * are available: + * + * Name Description + * -------------- ----------------------------------------------------- + * tol Paul Tol's qualitative scheme, cbf, max 12 colours. + * tol-dv Paul Tol's diverging scheme, cbf. + * tol-sq Paul Tol's sequential scheme, cbf. + * tol-rainbow Paul Tol's qualitative scheme, cbf. + * + * rainbow A rainbow palette. + * + * cb-YlGn ColorBrewer sequential schemes. + * cb-YlGnBu + * cb-GnBu + * cb-BuGn + * cb-PuBuGn + * cb-PuBu + * cb-BuPu + * cb-RdPu + * cb-PuRd + * cb-OrRd + * cb-YlOrRd + * cb-YlOrBr + * cb-Purples + * cb-Blues + * cb-Greens + * cb-Oranges + * cb-Reds + * cb-Greys + * + * cb-PuOr ColorBrewer diverging schemes. + * cb-BrBG + * cb-PRGn + * cb-PiYG + * cb-RdBu + * cb-RdGy + * cb-RdYlBu + * cb-Spectral + * cb-RdYlGn + * + * cb-Accent ColorBrewer qualitative schemes. + * cb-Dark2 + * cb-Paired + * cb-Pastel1 + * cb-Pastel2 + * cb-Set1 + * cb-Set2 + * cb-Set3 + * + * sol-base Solarized base colours. + * sol-accent Solarized accent colours. + * + * The following groups are also available by default: + * + * Name Description + * -------------- ----------------------------------------------------- + * all All registered schemes. + * sequential All sequential schemes. + * diverging All diverging schemes. + * qualitative All qualitative schemes. + * cb-sequential All ColorBrewer sequential schemes. + * cb-diverging All ColorBrewer diverging schemes. + * cb-qualitative All ColorBrewer qualitative schemes. + * + * You can read more about Paul Tol's palettes at http://www.sron.nl/~pault/. + * You can read more about ColorBrewer at http://colorbrewer2.org. + * + * @param {string|!Array<string>} name A name of a colour scheme, of + * a group of colour schemes, or an array of any of those. + * @param {number=} opt_number When requesting only colourblind friendly + * schemes, number of colours the scheme must provide generating such + * that the palette is still colourblind friendly. 2 by default. + * @return {!Array<!palette.SchemeType>} An array of colour scheme objects + * matching the criteria. Sorted by scheme name. + */ + palette.listSchemes = function(name, opt_number) { + if (!opt_number) { + opt_number = 2; + } else if (opt_number < 0) { + opt_number = -opt_number; + } + + var ret = []; + (typeof name === 'string' ? [name] : name).forEach(function(n) { + var cbf = n.substring(n.length - 4) === '-cbf'; + if (cbf) { + n = n.substring(0, n.length - 4); + } + var schemes = + registered_schemes['g-' + n] || + registered_schemes['n-' + n] || + []; + for (var i = 0, scheme; (scheme = schemes[i]); ++i) { + if ((cbf ? scheme.cbf : scheme.max) >= opt_number) { + ret.push(scheme); + } + } + }); + + ret.sort(function(a, b) { + return a.scheme_name >= b.scheme_name ? + a.scheme_name > b.scheme_name ? 1 : 0 : -1; + }); + return ret; + }; + + + /** + * Generates a palette using given colour generating function. + * + * The color_func callback must accept a singe number argument whose value + * can vary from 0.0 to 1.0 (or in general from opt_start to opt_end), and + * return a colour as an 'RRGGBB' string. This function will be used when + * generating palettes, i.e. if 11-colour palette is requested, this + * function will be called with arguments 0.0, 0.1, …, 1.0. + * + * In some cases, it is not desirable to reach 1.0 when generating + * a palette. This happens for hue-rainbows where the 0–1 range corresponds + * to a 0°–360° range in hues, and since hue at 0° is the same as at 360°, + * it's desired to stop short the end of the range when generating + * a palette. To accomplish this, opt_cyclic should be set to true. + * + * opt_start and opt_end may be used to change the range the colour + * generation function is called with. opt_end may be less than opt_start + * which will case to traverse the range in reverse. Another way to reverse + * the palette is requesting negative number of colours. The two methods do + * not always lead to the same results (especially if opt_cyclic is set). + * + * @param {palette.ColorFunction} color_func A colours generating callback + * function. + * @param {number} number Number of colours to generate in the palette. If + * number is negative, colours in the palette will be reversed. If only + * one colour is requested, colour at opt_start will be returned. + * @param {number=} opt_start Optional starting point for the palette + * generation function. Zero by default. + * @param {number=} opt_end Optional ending point for the palette generation + * function. One by default. + * @param {boolean=} opt_cyclic If true, function will assume colour at + * point opt_start is the same as one at opt_end. + * @return {palette.Palette} An array of 'RRGGBB' colours. + */ + palette.generate = function(color_func, number, opt_start, opt_end, + opt_cyclic) { + if (Math.abs(number) < 1) { + return []; + } + + opt_start = opt_start === void(0) ? 0 : opt_start; + opt_end = opt_end === void(0) ? 1 : opt_end; + + if (Math.abs(number) < 2) { + return [color_func(opt_start)]; + } + + var i = Math.abs(number); + var x = opt_start; + var ret = []; + var step = (opt_end - opt_start) / (opt_cyclic ? i : (i - 1)); + + for (; --i >= 0; x += step) { + ret.push(color_func(x)); + } + if (number < 0) { + ret.reverse(); + } + return ret; + }; + + + /** + * Clamps value to [0, 1] range. + * @param {number} v Number to limit value of. + * @return {number} If v is inside of [0, 1] range returns v, otherwise + * returns 0 or 1 depending which side of the range v is closer to. + */ + var clamp = function(v) { + return v > 0 ? (v < 1 ? v : 1) : 0; + }; + + /** + * Converts r, g, b triple into RRGGBB hex representation. + * @param {number} r Red value of the colour in the range [0, 1]. + * @param {number} g Green value of the colour in the range [0, 1]. + * @param {number} b Blue value of the colour in the range [0, 1]. + * @return {string} A lower-case RRGGBB representation of the colour. + */ + palette.rgbColor = function(r, g, b) { + return [r, g, b].map(function(v) { + v = Number(Math.round(clamp(v) * 255)).toString(16); + return v.length === 1 ? '0' + v : v; + }).join(''); + }; + + /** + * Converts a linear r, g, b triple into RRGGBB hex representation. + * @param {number} r Linear red value of the colour in the range [0, 1]. + * @param {number} g Linear green value of the colour in the range [0, 1]. + * @param {number} b Linear blue value of the colour in the range [0, 1]. + * @return {string} A lower-case RRGGBB representation of the colour. + */ + palette.linearRgbColor = function(r, g, b) { + // http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html + return [r, g, b].map(function(v) { + v = clamp(v); + if (v <= 0.0031308) { + v = 12.92 * v; + } else { + v = 1.055 * Math.pow(v, 1 / 2.4) - 0.055; + } + v = Number(Math.round(v * 255)).toString(16); + return v.length === 1 ? '0' + v : v; + }).join(''); + }; + + /** + * Converts an HSV colours to RRGGBB hex representation. + * @param {number} h Hue in the range [0, 1]. + * @param {number=} opt_s Saturation in the range [0, 1]. One by default. + * @param {number=} opt_v Value in the range [0, 1]. One by default. + * @return {string} An RRGGBB representation of the colour. + */ + palette.hsvColor = function(h, opt_s, opt_v) { + h *= 6; + var s = opt_s === void(0) ? 1 : clamp(opt_s); + var v = opt_v === void(0) ? 1 : clamp(opt_v); + var x = v * (1 - s * Math.abs(h % 2 - 1)); + var m = v * (1 - s); + switch (Math.floor(h) % 6) { + case 0: return palette.rgbColor(v, x, m); + case 1: return palette.rgbColor(x, v, m); + case 2: return palette.rgbColor(m, v, x); + case 3: return palette.rgbColor(m, x, v); + case 4: return palette.rgbColor(x, m, v); + default: return palette.rgbColor(v, m, x); + } + }; + + palette.register(palette.Scheme.withColorFunction( + 'rainbow', 'qualitative', palette.hsvColor, false, true)); + + return palette; + })(); + + + /** @typedef {function(number): string} */ + palette.ColorFunction; + + /** @typedef {!Array<string>} */ + palette.Palette; + + /** @typedef {!Object<number, palette.Palette>|!Array<palette.Palette>} */ + palette.PalettesList; + + /** + * @typedef { + * function(number, ...?): Array<string>| + * { + * scheme_name: string, + * groups: !Array<string>, + * max: number, + * cbf_max: number, + * addPalette: function(!Array<string>, boolean=), + * addPalettes: function(palette.PalettesList, number=, number=), + * shrinkByTakingHead: function(boolean, number=), + * setColorFunction: function(palette.ColorFunction, boolean=, boolean=), + * color: function(number, ...?): ?string}} + */ + palette.SchemeType; + + + /* mpn65 palette start here. ************************************************/ + + /* The ‘mpn65’ palette is designed for systems which show many graphs which + don’t have custom colour palettes chosen by humans or if number of necessary + colours isn’t know a priori. */ + + (function() { + var scheme = palette.Scheme.fromPalettes('mpn65', 'qualitative', [[ + 'ff0029', '377eb8', '66a61e', '984ea3', '00d2d5', 'ff7f00', 'af8d00', + '7f80cd', 'b3e900', 'c42e60', 'a65628', 'f781bf', '8dd3c7', 'bebada', + 'fb8072', '80b1d3', 'fdb462', 'fccde5', 'bc80bd', 'ffed6f', 'c4eaff', + 'cf8c00', '1b9e77', 'd95f02', 'e7298a', 'e6ab02', 'a6761d', '0097ff', + '00d067', '000000', '252525', '525252', '737373', '969696', 'bdbdbd', + 'f43600', '4ba93b', '5779bb', '927acc', '97ee3f', 'bf3947', '9f5b00', + 'f48758', '8caed6', 'f2b94f', 'eff26e', 'e43872', 'd9b100', '9d7a00', + '698cff', 'd9d9d9', '00d27e', 'd06800', '009f82', 'c49200', 'cbe8ff', + 'fecddf', 'c27eb6', '8cd2ce', 'c4b8d9', 'f883b0', 'a49100', 'f48800', + '27d0df', 'a04a9b' + ]]); + scheme.shrinkByTakingHead(true); + palette.register(scheme); + })(); + + /* Paul Tol's schemes start here. *******************************************/ + /* See http://www.sron.nl/~pault/ */ + + (function() { + var rgb = palette.rgbColor; + + /** + * Calculates value of a polynomial at given point. + * For example, poly(x, 1, 2, 3) calculates value of “1 + 2*x + 3*X²â€. + * @param {number} x Value to calculate polynomial for. + * @param {...number} varargs Coefficients of the polynomial specified in + * the order of rising powers of x including constant as the first + * variable argument. + */ + var poly = function(x, varargs) { + var i = arguments.length - 1, n = arguments[i]; + while (i > 1) { + n = n * x + arguments[--i]; + } + return n; + }; + + /** + * Calculate approximate value of error function with maximum error of 0.0005. + * See <https://en.wikipedia.org/wiki/Error_function>. + * @param {number} x Argument of the error function. + * @return {number} Value of error function for x. + */ + var erf = function(x) { + // https://en.wikipedia.org/wiki/Error_function#Approximation_with_elementary_functions + // This produces a maximum error of 0.0005 which is more then we need. In + // the worst case, that error is multiplied by four and then divided by two + // before being multiplied by 255, so in the end, the error is multiplied by + // 510 which produces 0.255 which is less than a single colour step. + var y = poly(Math.abs(x), 1, 0.278393, 0.230389, 0.000972, 0.078108); + y *= y; // y^2 + y *= y; // y^4 + y = 1 - 1 / y; + return x < 0 ? -y : y; + }; + + palette.register(palette.Scheme.fromPalettes('tol', 'qualitative', [ + ['4477aa'], + ['4477aa', 'cc6677'], + ['4477aa', 'ddcc77', 'cc6677'], + ['4477aa', '117733', 'ddcc77', 'cc6677'], + ['332288', '88ccee', '117733', 'ddcc77', 'cc6677'], + ['332288', '88ccee', '117733', 'ddcc77', 'cc6677', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', 'ddcc77', 'cc6677', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677', + 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', 'cc6677', + '882255', 'aa4499'], + ['332288', '88ccee', '44aa99', '117733', '999933', 'ddcc77', '661100', + 'cc6677', '882255', 'aa4499'], + ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77', + '661100', 'cc6677', '882255', 'aa4499'], + ['332288', '6699cc', '88ccee', '44aa99', '117733', '999933', 'ddcc77', + '661100', 'cc6677', 'aa4466', '882255', 'aa4499'] + ], 12, 12)); + + /** + * Calculates a colour along Paul Tol's sequential colours axis. + * See <http://www.sron.nl/~pault/colourschemes.pdf> figure 7 and equation 1. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolSequentialColor = function(x) { + return rgb(1 - 0.392 * (1 + erf((x - 0.869) / 0.255)), + 1.021 - 0.456 * (1 + erf((x - 0.527) / 0.376)), + 1 - 0.493 * (1 + erf((x - 0.272) / 0.309))); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-sq', 'sequential', palette.tolSequentialColor, true)); + + /** + * Calculates a colour along Paul Tol's diverging colours axis. + * See <http://www.sron.nl/~pault/colourschemes.pdf> figure 8 and equation 2. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolDivergingColor = function(x) { + var g = poly(x, 0.572, 1.524, -1.811) / poly(x, 1, -0.291, 0.1574); + return rgb(poly(x, 0.235, -2.13, 26.92, -65.5, 63.5, -22.36), + g * g, + 1 / poly(x, 1.579, -4.03, 12.92, -31.4, 48.6, -23.36)); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-dv', 'diverging', palette.tolDivergingColor, true)); + + /** + * Calculates a colour along Paul Tol's rainbow colours axis. + * See <http://www.sron.nl/~pault/colourschemes.pdf> figure 13 and equation 3. + * @param {number} x Position of the colour on the axis in the [0, 1] range. + * @return {string} An RRGGBB representation of the colour. + */ + palette.tolRainbowColor = function(x) { + return rgb(poly(x, 0.472, -0.567, 4.05) / poly(x, 1, 8.72, -19.17, 14.1), + poly(x, 0.108932, -1.22635, 27.284, -98.577, 163.3, -131.395, + 40.634), + 1 / poly(x, 1.97, 3.54, -68.5, 243, -297, 125)); + }; + + palette.register(palette.Scheme.withColorFunction( + 'tol-rainbow', 'qualitative', palette.tolRainbowColor, true)); + })(); + + + /* Solarized colour schemes start here. *************************************/ + /* See http://ethanschoonover.com/solarized */ + + (function() { + /* + * Those are not really designed to be used in graphs, but we're keeping + * them here in case someone cares. + */ + palette.register(palette.Scheme.fromPalettes('sol-base', 'sequential', [ + ['002b36', '073642', '586e75', '657b83', '839496', '93a1a1', 'eee8d5', + 'fdf6e3'] + ], 1, 8)); + palette.register(palette.Scheme.fromPalettes('sol-accent', 'qualitative', [ + ['b58900', 'cb4b16', 'dc322f', 'd33682', '6c71c4', '268bd2', '2aa198', + '859900'] + ])); + })(); + + + /* ColorBrewer colour schemes start here. ***********************************/ + /* See http://colorbrewer2.org/ */ + + (function() { + var schemes = { + YlGn: { + type: 'sequential', + cbf: 42, + 3: ['f7fcb9', 'addd8e', '31a354'], + 4: ['ffffcc', 'c2e699', '78c679', '238443'], + 5: ['ffffcc', 'c2e699', '78c679', '31a354', '006837'], + 6: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '31a354', '006837'], + 7: ['ffffcc', 'd9f0a3', 'addd8e', '78c679', '41ab5d', '238443', + '005a32'], + 8: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d', + '238443', '005a32'], + 9: ['ffffe5', 'f7fcb9', 'd9f0a3', 'addd8e', '78c679', '41ab5d', + '238443', '006837', '004529'] + }, + YlGnBu: { + type: 'sequential', + cbf: 42, + 3: ['edf8b1', '7fcdbb', '2c7fb8'], + 4: ['ffffcc', 'a1dab4', '41b6c4', '225ea8'], + 5: ['ffffcc', 'a1dab4', '41b6c4', '2c7fb8', '253494'], + 6: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '2c7fb8', '253494'], + 7: ['ffffcc', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', '225ea8', + '0c2c84'], + 8: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', + '225ea8', '0c2c84'], + 9: ['ffffd9', 'edf8b1', 'c7e9b4', '7fcdbb', '41b6c4', '1d91c0', + '225ea8', '253494', '081d58'] + }, + GnBu: { + type: 'sequential', + cbf: 42, + 3: ['e0f3db', 'a8ddb5', '43a2ca'], + 4: ['f0f9e8', 'bae4bc', '7bccc4', '2b8cbe'], + 5: ['f0f9e8', 'bae4bc', '7bccc4', '43a2ca', '0868ac'], + 6: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '43a2ca', '0868ac'], + 7: ['f0f9e8', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', '2b8cbe', + '08589e'], + 8: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', + '2b8cbe', '08589e'], + 9: ['f7fcf0', 'e0f3db', 'ccebc5', 'a8ddb5', '7bccc4', '4eb3d3', + '2b8cbe', '0868ac', '084081'] + }, + BuGn: { + type: 'sequential', + cbf: 42, + 3: ['e5f5f9', '99d8c9', '2ca25f'], + 4: ['edf8fb', 'b2e2e2', '66c2a4', '238b45'], + 5: ['edf8fb', 'b2e2e2', '66c2a4', '2ca25f', '006d2c'], + 6: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '2ca25f', '006d2c'], + 7: ['edf8fb', 'ccece6', '99d8c9', '66c2a4', '41ae76', '238b45', + '005824'], + 8: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76', + '238b45', '005824'], + 9: ['f7fcfd', 'e5f5f9', 'ccece6', '99d8c9', '66c2a4', '41ae76', + '238b45', '006d2c', '00441b'] + }, + PuBuGn: { + type: 'sequential', + cbf: 42, + 3: ['ece2f0', 'a6bddb', '1c9099'], + 4: ['f6eff7', 'bdc9e1', '67a9cf', '02818a'], + 5: ['f6eff7', 'bdc9e1', '67a9cf', '1c9099', '016c59'], + 6: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '1c9099', '016c59'], + 7: ['f6eff7', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', '02818a', + '016450'], + 8: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', + '02818a', '016450'], + 9: ['fff7fb', 'ece2f0', 'd0d1e6', 'a6bddb', '67a9cf', '3690c0', + '02818a', '016c59', '014636'] + }, + PuBu: { + type: 'sequential', + cbf: 42, + 3: ['ece7f2', 'a6bddb', '2b8cbe'], + 4: ['f1eef6', 'bdc9e1', '74a9cf', '0570b0'], + 5: ['f1eef6', 'bdc9e1', '74a9cf', '2b8cbe', '045a8d'], + 6: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '2b8cbe', '045a8d'], + 7: ['f1eef6', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', '0570b0', + '034e7b'], + 8: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', + '0570b0', '034e7b'], + 9: ['fff7fb', 'ece7f2', 'd0d1e6', 'a6bddb', '74a9cf', '3690c0', + '0570b0', '045a8d', '023858'] + }, + BuPu: { + type: 'sequential', + cbf: 42, + 3: ['e0ecf4', '9ebcda', '8856a7'], + 4: ['edf8fb', 'b3cde3', '8c96c6', '88419d'], + 5: ['edf8fb', 'b3cde3', '8c96c6', '8856a7', '810f7c'], + 6: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8856a7', '810f7c'], + 7: ['edf8fb', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', '88419d', + '6e016b'], + 8: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', + '88419d', '6e016b'], + 9: ['f7fcfd', 'e0ecf4', 'bfd3e6', '9ebcda', '8c96c6', '8c6bb1', + '88419d', '810f7c', '4d004b'] + }, + RdPu: { + type: 'sequential', + cbf: 42, + 3: ['fde0dd', 'fa9fb5', 'c51b8a'], + 4: ['feebe2', 'fbb4b9', 'f768a1', 'ae017e'], + 5: ['feebe2', 'fbb4b9', 'f768a1', 'c51b8a', '7a0177'], + 6: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'c51b8a', '7a0177'], + 7: ['feebe2', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', 'ae017e', + '7a0177'], + 8: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', + 'ae017e', '7a0177'], + 9: ['fff7f3', 'fde0dd', 'fcc5c0', 'fa9fb5', 'f768a1', 'dd3497', + 'ae017e', '7a0177', '49006a'] + }, + PuRd: { + type: 'sequential', + cbf: 42, + 3: ['e7e1ef', 'c994c7', 'dd1c77'], + 4: ['f1eef6', 'd7b5d8', 'df65b0', 'ce1256'], + 5: ['f1eef6', 'd7b5d8', 'df65b0', 'dd1c77', '980043'], + 6: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'dd1c77', '980043'], + 7: ['f1eef6', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', 'ce1256', + '91003f'], + 8: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', + 'ce1256', '91003f'], + 9: ['f7f4f9', 'e7e1ef', 'd4b9da', 'c994c7', 'df65b0', 'e7298a', + 'ce1256', '980043', '67001f'] + }, + OrRd: { + type: 'sequential', + cbf: 42, + 3: ['fee8c8', 'fdbb84', 'e34a33'], + 4: ['fef0d9', 'fdcc8a', 'fc8d59', 'd7301f'], + 5: ['fef0d9', 'fdcc8a', 'fc8d59', 'e34a33', 'b30000'], + 6: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'e34a33', 'b30000'], + 7: ['fef0d9', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', 'd7301f', + '990000'], + 8: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', + 'd7301f', '990000'], + 9: ['fff7ec', 'fee8c8', 'fdd49e', 'fdbb84', 'fc8d59', 'ef6548', + 'd7301f', 'b30000', '7f0000'] + }, + YlOrRd: { + type: 'sequential', + cbf: 42, + 3: ['ffeda0', 'feb24c', 'f03b20'], + 4: ['ffffb2', 'fecc5c', 'fd8d3c', 'e31a1c'], + 5: ['ffffb2', 'fecc5c', 'fd8d3c', 'f03b20', 'bd0026'], + 6: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'f03b20', 'bd0026'], + 7: ['ffffb2', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', 'e31a1c', + 'b10026'], + 8: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', + 'e31a1c', 'b10026'], + 9: ['ffffcc', 'ffeda0', 'fed976', 'feb24c', 'fd8d3c', 'fc4e2a', + 'e31a1c', 'bd0026', '800026'] + }, + YlOrBr: { + type: 'sequential', + cbf: 42, + 3: ['fff7bc', 'fec44f', 'd95f0e'], + 4: ['ffffd4', 'fed98e', 'fe9929', 'cc4c02'], + 5: ['ffffd4', 'fed98e', 'fe9929', 'd95f0e', '993404'], + 6: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'd95f0e', '993404'], + 7: ['ffffd4', 'fee391', 'fec44f', 'fe9929', 'ec7014', 'cc4c02', + '8c2d04'], + 8: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014', + 'cc4c02', '8c2d04'], + 9: ['ffffe5', 'fff7bc', 'fee391', 'fec44f', 'fe9929', 'ec7014', + 'cc4c02', '993404', '662506'] + }, + Purples: { + type: 'sequential', + cbf: 42, + 3: ['efedf5', 'bcbddc', '756bb1'], + 4: ['f2f0f7', 'cbc9e2', '9e9ac8', '6a51a3'], + 5: ['f2f0f7', 'cbc9e2', '9e9ac8', '756bb1', '54278f'], + 6: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '756bb1', '54278f'], + 7: ['f2f0f7', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', '6a51a3', + '4a1486'], + 8: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', + '6a51a3', '4a1486'], + 9: ['fcfbfd', 'efedf5', 'dadaeb', 'bcbddc', '9e9ac8', '807dba', + '6a51a3', '54278f', '3f007d'] + }, + Blues: { + type: 'sequential', + cbf: 42, + 3: ['deebf7', '9ecae1', '3182bd'], + 4: ['eff3ff', 'bdd7e7', '6baed6', '2171b5'], + 5: ['eff3ff', 'bdd7e7', '6baed6', '3182bd', '08519c'], + 6: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '3182bd', '08519c'], + 7: ['eff3ff', 'c6dbef', '9ecae1', '6baed6', '4292c6', '2171b5', + '084594'], + 8: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6', + '2171b5', '084594'], + 9: ['f7fbff', 'deebf7', 'c6dbef', '9ecae1', '6baed6', '4292c6', + '2171b5', '08519c', '08306b'] + }, + Greens: { + type: 'sequential', + cbf: 42, + 3: ['e5f5e0', 'a1d99b', '31a354'], + 4: ['edf8e9', 'bae4b3', '74c476', '238b45'], + 5: ['edf8e9', 'bae4b3', '74c476', '31a354', '006d2c'], + 6: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '31a354', '006d2c'], + 7: ['edf8e9', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', '238b45', + '005a32'], + 8: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', + '238b45', '005a32'], + 9: ['f7fcf5', 'e5f5e0', 'c7e9c0', 'a1d99b', '74c476', '41ab5d', + '238b45', '006d2c', '00441b'] + }, + Oranges: { + type: 'sequential', + cbf: 42, + 3: ['fee6ce', 'fdae6b', 'e6550d'], + 4: ['feedde', 'fdbe85', 'fd8d3c', 'd94701'], + 5: ['feedde', 'fdbe85', 'fd8d3c', 'e6550d', 'a63603'], + 6: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'e6550d', 'a63603'], + 7: ['feedde', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', 'd94801', + '8c2d04'], + 8: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', + 'd94801', '8c2d04'], + 9: ['fff5eb', 'fee6ce', 'fdd0a2', 'fdae6b', 'fd8d3c', 'f16913', + 'd94801', 'a63603', '7f2704'] + }, + Reds: { + type: 'sequential', + cbf: 42, + 3: ['fee0d2', 'fc9272', 'de2d26'], + 4: ['fee5d9', 'fcae91', 'fb6a4a', 'cb181d'], + 5: ['fee5d9', 'fcae91', 'fb6a4a', 'de2d26', 'a50f15'], + 6: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'de2d26', 'a50f15'], + 7: ['fee5d9', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', 'cb181d', + '99000d'], + 8: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', + 'cb181d', '99000d'], + 9: ['fff5f0', 'fee0d2', 'fcbba1', 'fc9272', 'fb6a4a', 'ef3b2c', + 'cb181d', 'a50f15', '67000d'] + }, + Greys: { + type: 'sequential', + cbf: 42, + 3: ['f0f0f0', 'bdbdbd', '636363'], + 4: ['f7f7f7', 'cccccc', '969696', '525252'], + 5: ['f7f7f7', 'cccccc', '969696', '636363', '252525'], + 6: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '636363', '252525'], + 7: ['f7f7f7', 'd9d9d9', 'bdbdbd', '969696', '737373', '525252', + '252525'], + 8: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373', + '525252', '252525'], + 9: ['ffffff', 'f0f0f0', 'd9d9d9', 'bdbdbd', '969696', '737373', + '525252', '252525', '000000'] + }, + PuOr: { + type: 'diverging', + cbf: 42, + 3: ['f1a340', 'f7f7f7', '998ec3'], + 4: ['e66101', 'fdb863', 'b2abd2', '5e3c99'], + 5: ['e66101', 'fdb863', 'f7f7f7', 'b2abd2', '5e3c99'], + 6: ['b35806', 'f1a340', 'fee0b6', 'd8daeb', '998ec3', '542788'], + 7: ['b35806', 'f1a340', 'fee0b6', 'f7f7f7', 'd8daeb', '998ec3', + '542788'], + 8: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb', 'b2abd2', + '8073ac', '542788'], + 9: ['b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7', 'd8daeb', + 'b2abd2', '8073ac', '542788'], + 10: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'd8daeb', + 'b2abd2', '8073ac', '542788', '2d004b'], + 11: ['7f3b08', 'b35806', 'e08214', 'fdb863', 'fee0b6', 'f7f7f7', + 'd8daeb', 'b2abd2', '8073ac', '542788', '2d004b'] + }, + BrBG: { + type: 'diverging', + cbf: 42, + 3: ['d8b365', 'f5f5f5', '5ab4ac'], + 4: ['a6611a', 'dfc27d', '80cdc1', '018571'], + 5: ['a6611a', 'dfc27d', 'f5f5f5', '80cdc1', '018571'], + 6: ['8c510a', 'd8b365', 'f6e8c3', 'c7eae5', '5ab4ac', '01665e'], + 7: ['8c510a', 'd8b365', 'f6e8c3', 'f5f5f5', 'c7eae5', '5ab4ac', + '01665e'], + 8: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5', '80cdc1', + '35978f', '01665e'], + 9: ['8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5', 'c7eae5', + '80cdc1', '35978f', '01665e'], + 10: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'c7eae5', + '80cdc1', '35978f', '01665e', '003c30'], + 11: ['543005', '8c510a', 'bf812d', 'dfc27d', 'f6e8c3', 'f5f5f5', + 'c7eae5', '80cdc1', '35978f', '01665e', '003c30'] + }, + PRGn: { + type: 'diverging', + cbf: 42, + 3: ['af8dc3', 'f7f7f7', '7fbf7b'], + 4: ['7b3294', 'c2a5cf', 'a6dba0', '008837'], + 5: ['7b3294', 'c2a5cf', 'f7f7f7', 'a6dba0', '008837'], + 6: ['762a83', 'af8dc3', 'e7d4e8', 'd9f0d3', '7fbf7b', '1b7837'], + 7: ['762a83', 'af8dc3', 'e7d4e8', 'f7f7f7', 'd9f0d3', '7fbf7b', + '1b7837'], + 8: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3', 'a6dba0', + '5aae61', '1b7837'], + 9: ['762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7', 'd9f0d3', + 'a6dba0', '5aae61', '1b7837'], + 10: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'd9f0d3', + 'a6dba0', '5aae61', '1b7837', '00441b'], + 11: ['40004b', '762a83', '9970ab', 'c2a5cf', 'e7d4e8', 'f7f7f7', + 'd9f0d3', 'a6dba0', '5aae61', '1b7837', '00441b'] + }, + PiYG: { + type: 'diverging', + cbf: 42, + 3: ['e9a3c9', 'f7f7f7', 'a1d76a'], + 4: ['d01c8b', 'f1b6da', 'b8e186', '4dac26'], + 5: ['d01c8b', 'f1b6da', 'f7f7f7', 'b8e186', '4dac26'], + 6: ['c51b7d', 'e9a3c9', 'fde0ef', 'e6f5d0', 'a1d76a', '4d9221'], + 7: ['c51b7d', 'e9a3c9', 'fde0ef', 'f7f7f7', 'e6f5d0', 'a1d76a', + '4d9221'], + 8: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0', 'b8e186', + '7fbc41', '4d9221'], + 9: ['c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7', 'e6f5d0', + 'b8e186', '7fbc41', '4d9221'], + 10: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'e6f5d0', + 'b8e186', '7fbc41', '4d9221', '276419'], + 11: ['8e0152', 'c51b7d', 'de77ae', 'f1b6da', 'fde0ef', 'f7f7f7', + 'e6f5d0', 'b8e186', '7fbc41', '4d9221', '276419'] + }, + RdBu: { + type: 'diverging', + cbf: 42, + 3: ['ef8a62', 'f7f7f7', '67a9cf'], + 4: ['ca0020', 'f4a582', '92c5de', '0571b0'], + 5: ['ca0020', 'f4a582', 'f7f7f7', '92c5de', '0571b0'], + 6: ['b2182b', 'ef8a62', 'fddbc7', 'd1e5f0', '67a9cf', '2166ac'], + 7: ['b2182b', 'ef8a62', 'fddbc7', 'f7f7f7', 'd1e5f0', '67a9cf', + '2166ac'], + 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0', '92c5de', + '4393c3', '2166ac'], + 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7', 'd1e5f0', + '92c5de', '4393c3', '2166ac'], + 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'd1e5f0', + '92c5de', '4393c3', '2166ac', '053061'], + 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'f7f7f7', + 'd1e5f0', '92c5de', '4393c3', '2166ac', '053061'] + }, + RdGy: { + type: 'diverging', + cbf: 42, + 3: ['ef8a62', 'ffffff', '999999'], + 4: ['ca0020', 'f4a582', 'bababa', '404040'], + 5: ['ca0020', 'f4a582', 'ffffff', 'bababa', '404040'], + 6: ['b2182b', 'ef8a62', 'fddbc7', 'e0e0e0', '999999', '4d4d4d'], + 7: ['b2182b', 'ef8a62', 'fddbc7', 'ffffff', 'e0e0e0', '999999', + '4d4d4d'], + 8: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0', 'bababa', + '878787', '4d4d4d'], + 9: ['b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff', 'e0e0e0', + 'bababa', '878787', '4d4d4d'], + 10: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'e0e0e0', + 'bababa', '878787', '4d4d4d', '1a1a1a'], + 11: ['67001f', 'b2182b', 'd6604d', 'f4a582', 'fddbc7', 'ffffff', + 'e0e0e0', 'bababa', '878787', '4d4d4d', '1a1a1a'] + }, + RdYlBu: { + type: 'diverging', + cbf: 42, + 3: ['fc8d59', 'ffffbf', '91bfdb'], + 4: ['d7191c', 'fdae61', 'abd9e9', '2c7bb6'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'abd9e9', '2c7bb6'], + 6: ['d73027', 'fc8d59', 'fee090', 'e0f3f8', '91bfdb', '4575b4'], + 7: ['d73027', 'fc8d59', 'fee090', 'ffffbf', 'e0f3f8', '91bfdb', + '4575b4'], + 8: ['d73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', 'abd9e9', + '74add1', '4575b4'], + 9: ['d73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf', 'e0f3f8', + 'abd9e9', '74add1', '4575b4'], + 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'e0f3f8', + 'abd9e9', '74add1', '4575b4', '313695'], + 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee090', 'ffffbf', + 'e0f3f8', 'abd9e9', '74add1', '4575b4', '313695'] + }, + Spectral: { + type: 'diverging', + cbf: 0, + 3: ['fc8d59', 'ffffbf', '99d594'], + 4: ['d7191c', 'fdae61', 'abdda4', '2b83ba'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'abdda4', '2b83ba'], + 6: ['d53e4f', 'fc8d59', 'fee08b', 'e6f598', '99d594', '3288bd'], + 7: ['d53e4f', 'fc8d59', 'fee08b', 'ffffbf', 'e6f598', '99d594', + '3288bd'], + 8: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598', 'abdda4', + '66c2a5', '3288bd'], + 9: ['d53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'e6f598', + 'abdda4', '66c2a5', '3288bd'], + 10: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'e6f598', + 'abdda4', '66c2a5', '3288bd', '5e4fa2'], + 11: ['9e0142', 'd53e4f', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', + 'e6f598', 'abdda4', '66c2a5', '3288bd', '5e4fa2'] + }, + RdYlGn: { + type: 'diverging', + cbf: 0, + 3: ['fc8d59', 'ffffbf', '91cf60'], + 4: ['d7191c', 'fdae61', 'a6d96a', '1a9641'], + 5: ['d7191c', 'fdae61', 'ffffbf', 'a6d96a', '1a9641'], + 6: ['d73027', 'fc8d59', 'fee08b', 'd9ef8b', '91cf60', '1a9850'], + 7: ['d73027', 'fc8d59', 'fee08b', 'ffffbf', 'd9ef8b', '91cf60', + '1a9850'], + 8: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b', 'a6d96a', + '66bd63', '1a9850'], + 9: ['d73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', 'd9ef8b', + 'a6d96a', '66bd63', '1a9850'], + 10: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'd9ef8b', + 'a6d96a', '66bd63', '1a9850', '006837'], + 11: ['a50026', 'd73027', 'f46d43', 'fdae61', 'fee08b', 'ffffbf', + 'd9ef8b', 'a6d96a', '66bd63', '1a9850', '006837'] + }, + Accent: { + type: 'qualitative', + cbf: 0, + 3: ['7fc97f', 'beaed4', 'fdc086'], + 4: ['7fc97f', 'beaed4', 'fdc086', 'ffff99'], + 5: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0'], + 6: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f'], + 7: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f', + 'bf5b17'], + 8: ['7fc97f', 'beaed4', 'fdc086', 'ffff99', '386cb0', 'f0027f', + 'bf5b17', '666666'] + }, + Dark2: { + type: 'qualitative', + cbf: 3, + 3: ['1b9e77', 'd95f02', '7570b3'], + 4: ['1b9e77', 'd95f02', '7570b3', 'e7298a'], + 5: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e'], + 6: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02'], + 7: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02', + 'a6761d'], + 8: ['1b9e77', 'd95f02', '7570b3', 'e7298a', '66a61e', 'e6ab02', + 'a6761d', '666666'] + }, + Paired: { + type: 'qualitative', + cbf: 4, + 3: ['a6cee3', '1f78b4', 'b2df8a'], + 4: ['a6cee3', '1f78b4', 'b2df8a', '33a02c'], + 5: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99'], + 6: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c'], + 7: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f'], + 8: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00'], + 9: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6'], + 10: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a'], + 11: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99'], + 12: ['a6cee3', '1f78b4', 'b2df8a', '33a02c', 'fb9a99', 'e31a1c', + 'fdbf6f', 'ff7f00', 'cab2d6', '6a3d9a', 'ffff99', 'b15928'] + }, + Pastel1: { + type: 'qualitative', + cbf: 0, + 3: ['fbb4ae', 'b3cde3', 'ccebc5'], + 4: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4'], + 5: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6'], + 6: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc'], + 7: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd'], + 8: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd', 'fddaec'], + 9: ['fbb4ae', 'b3cde3', 'ccebc5', 'decbe4', 'fed9a6', 'ffffcc', + 'e5d8bd', 'fddaec', 'f2f2f2'] + }, + Pastel2: { + type: 'qualitative', + cbf: 0, + 3: ['b3e2cd', 'fdcdac', 'cbd5e8'], + 4: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4'], + 5: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9'], + 6: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae'], + 7: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae', + 'f1e2cc'], + 8: ['b3e2cd', 'fdcdac', 'cbd5e8', 'f4cae4', 'e6f5c9', 'fff2ae', + 'f1e2cc', 'cccccc'] + }, + Set1: { + type: 'qualitative', + cbf: 0, + 3: ['e41a1c', '377eb8', '4daf4a'], + 4: ['e41a1c', '377eb8', '4daf4a', '984ea3'], + 5: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00'], + 6: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33'], + 7: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628'], + 8: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628', 'f781bf'], + 9: ['e41a1c', '377eb8', '4daf4a', '984ea3', 'ff7f00', 'ffff33', + 'a65628', 'f781bf', '999999'] + }, + Set2: { + type: 'qualitative', + cbf: 3, + 3: ['66c2a5', 'fc8d62', '8da0cb'], + 4: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3'], + 5: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854'], + 6: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f'], + 7: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f', + 'e5c494'], + 8: ['66c2a5', 'fc8d62', '8da0cb', 'e78ac3', 'a6d854', 'ffd92f', + 'e5c494', 'b3b3b3'] + }, + Set3: { + type: 'qualitative', + cbf: 0, + 3: ['8dd3c7', 'ffffb3', 'bebada'], + 4: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072'], + 5: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3'], + 6: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462'], + 7: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69'], + 8: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5'], + 9: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9'], + 10: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd'], + 11: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5'], + 12: ['8dd3c7', 'ffffb3', 'bebada', 'fb8072', '80b1d3', 'fdb462', + 'b3de69', 'fccde5', 'd9d9d9', 'bc80bd', 'ccebc5', 'ffed6f'] + }, + Custom1: { + type: 'qualitative', + cbf: 0, + 1: ['3aa5ff'], + 2: ['42d4f4', 'f032e6'], + 3: ['f96a00', 'ffa866', 'ffd4b3'], + 5: ['ffcd22', '911db4', '373737','ff0058','469990'], + 6: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4'], + 7: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4'], + 8: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990'], + 9: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff'], + 10: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324'], + 11: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324','fffac8'], + 12: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324','fffac8','800000'], + 18: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324','fffac8','800000','aaffc3','808001', + 'bfef45','000075','fabebe','a9a9a9'], + 20: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324','fffac8','800000','aaffc3','808001', + 'bfef45','000075','fabebe','a9a9a9','000000','f032e6','ffd7b1'], + 50: ['e6194b','3cb44b','ffcd22','4364d8','f58230','911db4', '42d4f4', + '469990','e6bdff','9a6324','fffac8','800000','aaffc3','808001', + 'bfef45','000075','fabebe','a9a9a9','000000','f032e6','ffd7b1'], + }, + BasePalette: { + type: 'qualitative', + cbf: 0, + 1: ['024BA3'], + 2: ['024BA3', 'E24577'], + 3: ['024BA3', 'E24577', 'F58834'], + 5: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC'], + 6: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4'], + 7: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B'], + 8: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770'], + 9: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94'], + 10: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577'], + 11: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577', 'F56155'], + 12: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577', 'F56155', 'F58834'], + 18: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834'], + 20: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4'], + 50: ['024BA3', '0068BE', '0082CB', '019AC9', '02B1BC', '00C6A4', '00D88B', '8AE770', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4'], + }, + BasePalette2: { + type: 'qualitative', + cbf: 0, + 1: ['F3457E'], + 2: ['024BA3', '7B47A4'], + 3: ['024BA3', '7B47A4', 'B93F94'], + 5: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155'], + 6: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834'], + 7: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3'], + 8: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4'], + 9: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94'], + 10: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577'], + 11: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155'], + 12: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834'], + 18: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834'], + 20: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4'], + 50: ['024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4', 'B93F94', 'E24577', 'F56155', 'F58834', '024BA3', '7B47A4'], + }, + }; + + for (var name in schemes) { + var scheme = schemes[name]; + scheme = palette.Scheme.fromPalettes( + 'cb-' + name, [scheme.type, 'cb-' + scheme.type], scheme, 12, scheme.cbf); + palette.register(scheme); + } + })(); + + if(typeof module === "object" && module.exports) { + module.exports = palette + } + \ No newline at end of file diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx index 73a78d54a0ccdf8a82bd8872a66dae4dac236e30..e78165ecbe458325905ead9f5958f334139ef5b3 100644 --- a/src/components/common/Header.tsx +++ b/src/components/common/Header.tsx @@ -11,72 +11,109 @@ import { Breadcrumbs } from "./Breadcrumbs"; * Header component */ interface LoginProps { - history: any - breadCrumb?: any + history: any; + breadCrumb?: any; } interface LoginState { - userName: any + userName: any; + userInfo: any; } class Header extends Component<LoginProps, LoginState> { constructor(props: any) { super(props); this.state = { - userName: Auth.get('username'), + userName: Auth.get("username"), + userInfo: {}, }; this.logout = this.logout.bind(this); } + componentDidMount() { + let userDetails: any = localStorage.getItem("user"); + + let userObj = { + firstName: JSON.parse(userDetails).firstName, + lastName: JSON.parse(userDetails).lastName, + }; + + this.setState({ + userInfo: userObj, + }); + } + getUserInitials(userName: string) { // console.log('userName: ', userName) + if (userName) { - const userNameArr = userName.split('.').slice(0, 2) - return userNameArr.map((u) => u[0]).join('').toUpperCase() + const userNameArr = userName.split(".").slice(0, 2); + return userNameArr + .map((u) => u[0]) + .join("") + .toUpperCase(); } else { - return '' + return ""; } } logout() { UserService.logout(); this.props.history.push("/login"); - }; + } render() { return ( <Fragment> <div className="container-fluid white-bg"> - <div className="container top-header"> - <div - className="row" - > + <div className="row"> <div className="col-6 pt-3"> - <img src="./../../img/smf-header-logo.svg" className="img-fluid" alt="SMF logo" /> + <img + src="./../../img/smf-header-logo.svg" + className="img-fluid" + alt="SMF logo" + /> </div> <div className="col-6 pt-3"> - <div className="dropdown"> - <div className="float-right user-name-avatar" - role="button" - id="dropdownMenuLinkThree" - data-toggle="dropdown" - aria-haspopup="true" - aria-expanded="false"> - <span> - {this.getUserInitials(this.state.userName && this.state.userName || 'G')} - </span> - </div> - <div - className="dropdown-menu profileDropdown mr-5 cursorStyleOne" - aria-labelledby="dropdownMenuLinkThree" - > - - <p - className="dropdown-item dateFilterTextColor" - onClick={this.logout} + <div className=""> + <div className="dropdown"> + <div + className="float-right user-name-avatar" + role="button" + id="dropdownMenuLinkThree" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" > - Logout - </p> + <span> + {this.getUserInitials( + (this.state.userName && this.state.userName) || "G" + )} + </span> + </div> + <ul + className="dropdown-menu cursorStyleOne custom-dropdown-1 dropdown-menu-right" + aria-labelledby="dropdownMenuLinkThree" + > + <label className="username-1 px-4 pb-0 mb-0"> + {Object.keys(this.state.userInfo) + ? this.state.userInfo.firstName + + " " + + this.state.userInfo.lastName + : ""} + </label> + <br /> + <label className="email-1 px-4 "> + {this.state.userName && this.state.userName} + </label> + <br /> + <label + className="dropdown-item px-4 cursorStyleOne" + onClick={this.logout} + > + Logout + </label> + </ul> </div> </div> </div> @@ -86,69 +123,130 @@ class Header extends Component<LoginProps, LoginState> { <div className="row divider custom-divider"></div> </div> <div className="container top-header"> - <div - className="row h100" - > - { - (this.props.history.location.pathname.match("/dashboard") || + <div className="row h100"> + {(this.props.history.location.pathname.match("/dashboard") || !this.props.breadCrumb || - !this.props.breadCrumb.length) && + !this.props.breadCrumb.length) && ( <ul className="smf-menu mt-3"> - <li className="mr-5 active"> - <Link to={"/dashboard"} className={`${this.props.history.location.pathname.match("/dashboard") - ? "active" - : "" - }`}>HOME</Link> - </li> - {Helper.getUserRole() === APP.ROLE.INSTITUTION && ( - <li className="mr-5"> - <Link to={"/applications"} className={`${this.props.history.location.pathname.match("/applications") - ? "active" - : "" - }`}>MY APPLICATIONS</Link> - </li> - )} - {Helper.getUserRole() === APP.ROLE.INSTITUTION && ( - <li className="mr-5"> - <Link to={"/available-forms"} className={`${this.props.history.location.pathname.match("/available-forms") - ? "active" - : "" - }`}>AVAILABLE FORMS</Link> + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5 active"> + <Link + to={"/dashboard"} + className={`${ + this.props.history.location.pathname.match("/dashboard") + ? "active" + : "" + }`} + > + HOME + </Link> </li> - )} - {Helper.getUserRole() === APP.ROLE.REGULATOR && ( - <> - <li className="mr-5"> - <Link to={"/reviewer/all-applications"} className={`${this.props.history.location.pathname.match("/all-applications") - ? "active" - : "" - }`}>ALL APPLICATIONS</Link> + {Helper.getUserRole() === APP.ROLE.INSTITUTION && ( + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5"> + <Link + to={"/applications"} + className={`${ + this.props.history.location.pathname.match( + "/applications" + ) + ? "active" + : "" + }`} + > + MY APPLICATIONS + </Link> </li> - <li className="mr-5"> - <Link to={"/manage"} className={ - `${this.props.history.location.pathname.match("/manage") || - this.props.history.location.pathname.match("/forms") - ? "active" - : "" - }`}>MANAGE</Link> + )} + {Helper.getUserRole() === APP.ROLE.INSTITUTION && ( + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5"> + <Link + to={"/available-forms"} + className={`${ + this.props.history.location.pathname.match( + "/available-forms" + ) + ? "active" + : "" + }`} + > + AVAILABLE FORMS + </Link> </li> - </> - )} - {Helper.getUserRole() === APP.ROLE.INSPECTOR && ( - <li className="mr-5"> - <Link to={"/all-applications"} className={`${this.props.history.location.pathname.match("/all-applications") - ? "active" - : "" - }`}>ALL APPLICATIONS</Link> - </li> + )} + {Helper.getUserRole() === APP.ROLE.REGULATOR && ( + <> + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5"> + <Link + to={"/reviewer/all-applications"} + className={`${ + this.props.history.location.pathname.match( + "/all-applications" + ) + ? "active" + : "" + }`} + > + ALL APPLICATIONS + </Link> + </li> + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5"> + <Link + to={"/manage"} + className={`${ + this.props.history.location.pathname.match( + "/manage" + ) || + this.props.history.location.pathname.match("/forms") + ? "active" + : "" + }`} + > + MANAGE + </Link> + </li> + </> + )} + {Helper.getUserRole() === APP.ROLE.INSPECTOR && ( + <li className="mr-3 mr-sm-2 mr-md-5 mr-lg-5"> + <Link + to={"/all-applications"} + className={`${ + this.props.history.location.pathname.match( + "/all-applications" + ) + ? "active" + : "" + }`} + > + ALL APPLICATIONS + </Link> + </li> + )} + {Helper.getUserRole() === APP.ROLE.REGULATOR && ( + <li className=""> + <Link + to={"/analytics"} + className={`${ + this.props.history.location.pathname.match( + "/analytics" + ) + ? "active" + : "" + }`} + > + DASHBOARD + </Link> + </li> + )} + </ul> + )} + {!this.props.history.location.pathname.match("/dashboard") && + this.props.breadCrumb && + this.props.breadCrumb.length > 0 && ( + <Breadcrumbs + data={this.props.breadCrumb} + historyData={this.props.history} + /> )} - </ul> - } - { - !this.props.history.location.pathname.match("/dashboard") && this.props.breadCrumb && (this.props.breadCrumb.length > 0) && - <Breadcrumbs data={this.props.breadCrumb} historyData={this.props.history}/> - } - </div> </div> </div> diff --git a/src/components/dashboard/Dashboard.js b/src/components/dashboard/Dashboard.js index 9202fd7d0543db2f0e8165709309917981aa9617..230f998e72ad2938adc2faad052f28c1ffcf4075 100644 --- a/src/components/dashboard/Dashboard.js +++ b/src/components/dashboard/Dashboard.js @@ -21,6 +21,8 @@ class Dashboard extends Component { forms: [], myApplications: [], }; + + this.getInstituteApplications = this.getInstituteApplications.bind(); } formatDate(dateParam) { @@ -35,6 +37,12 @@ class Dashboard extends Component { } componentDidMount() { + setTimeout(() => { + this.getInstituteApplications(); + }, 500); + } + + getInstituteApplications = () => { if (Helper.getUserRole() === APP.ROLE.INSTITUTION) { FormService.get().then( (response) => { @@ -58,17 +66,15 @@ class Dashboard extends Component { ); // my applications section const myApplicationsReq = { - "searchObjects": [ - - ] - } + searchObjects: [], + }; FormService.getAllApplications(myApplicationsReq).then( (response2) => { if (response2.statusInfo.statusCode === APP.CODE.SUCCESS) { this.setState({ myApplications: - response2.responseData.length > 6 - ? response2.responseData.splice(0, 6) + response2.responseData.length > 8 + ? response2.responseData.splice(0, 8) : response2.responseData, }); // console.log(response2.responseData); @@ -83,7 +89,7 @@ class Dashboard extends Component { } ); } - } + }; render() { return ( @@ -135,10 +141,7 @@ class Dashboard extends Component { btnText="View application" isLink={true} link={ - "/applications/" + - i.formId - + "/" + - i.applicationId + "/applications/" + i.formId + "/" + i.applicationId } /> </div> @@ -159,7 +162,9 @@ class Dashboard extends Component { <div className="col-md-2 col-sm-12 col-12 pt-5"> <button className="btn btn-default smf-btn-default float-right mr-0" - onClick={(e) => this.props.history.push("/available-forms")} + onClick={(e) => + this.props.history.push("/available-forms") + } > SEE ALL </button> diff --git a/src/components/form-elements/BooleanField.tsx b/src/components/form-elements/BooleanField.tsx index a734d1e086aacd4dd4d041e7ea963a5780af0b8a..e8dc18250e86461818e85fa4d8a3c7ae6bfb4d15 100644 --- a/src/components/form-elements/BooleanField.tsx +++ b/src/components/form-elements/BooleanField.tsx @@ -31,8 +31,15 @@ export const BooleanField = ({ <br /> </> )} + <label className="switch"> - <input type="checkbox" id={"field-" + label} name={"field_" + label} disabled={isReadOnly}/> + <input + type="checkbox" + id={"field-" + label} + name={"field_" + label} + disabled={isReadOnly} + checked={value === "booleanTrue" ? true : false} + /> <span className="slider round"></span> </label> </div> diff --git a/src/components/form-elements/FileUploadView.module.css b/src/components/form-elements/FileUploadView.module.css index 73d427ef07f90a1d5a8af407dd6716b415f16b48..4bbffa6f11a313569e2672365fce0b58d431adfe 100644 --- a/src/components/form-elements/FileUploadView.module.css +++ b/src/components/form-elements/FileUploadView.module.css @@ -16,6 +16,10 @@ font-size: 0.875rem; letter-spacing: 0.25px; line-height: 1.429; + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .file_upload_list a { diff --git a/src/components/form-elements/FileUploadView.tsx b/src/components/form-elements/FileUploadView.tsx index 51e639e151335cdd867d1580b5c3753274ed210a..96e234cd29d62e20b838341c771c23d4342e736c 100644 --- a/src/components/form-elements/FileUploadView.tsx +++ b/src/components/form-elements/FileUploadView.tsx @@ -40,10 +40,10 @@ export const FileUploadView = ({ <div className={`${styles.file_upload_list} row p-0 m-0 p-1 m-2`} > - <div className="col-6"> + <div className="col-sm-12 col-md-12 col-lg-6"> <p className="float-start p-0 m-0">{i.split("/")[5]}</p> </div> - <div className="col-6"> + <div className="col-sm-12 col-md-12 col-lg-6"> <a className="float-end p-0 m-0" target="_blank" href={i} rel="noreferrer"> Preview on new tab </a> diff --git a/src/components/form-elements/InspectCheckOne.module.css b/src/components/form-elements/InspectCheckOne.module.css index f6001b9201659cebd7d8d971b5bacf4912cd2f4d..d56bc8159bdbf35408e81f15f6c5e312b7b416cb 100644 --- a/src/components/form-elements/InspectCheckOne.module.css +++ b/src/components/form-elements/InspectCheckOne.module.css @@ -2,40 +2,63 @@ @import url("../../assets/fonts/fonts.css"); .inspect_check_one { - background-color: var(--black-04); - border: 1px solid var(--black-08); - border-radius: 4px; - min-height: 8.25rem; + background-color: var(--black-04); + border: 1px solid var(--black-08); + border-radius: 4px; + min-height: 8.25rem; } .inspect_check_one label { - color: var(--black-60); - font-family: "Lato-Bold"; - font-size: 0.875rem; - letter-spacing: 0.25px; - line-height: 1.429; - cursor: pointer; + color: var(--black-60); + font-family: "Lato-Bold"; + font-size: 0.875rem; + letter-spacing: 0.25px; + line-height: 1.429; + cursor: pointer; } .inspect_check_one_custom_label_one { - color: var(--black-60); - font-family: "Lato-Bold"; - font-size: 0.875rem; - letter-spacing: 0.5px; - line-height: 1.429; + color: var(--black-60); + font-family: "Lato-Bold"; + font-size: 0.875rem; + letter-spacing: 0.5px; + line-height: 1.429; } .inspect_check_one_comments { - background-color: var(--white-100); - color: var(--black-60); - font-family: "Lato-Regular"; - font-size: 0.875rem; - letter-spacing: 0.25px; - line-height: 1.5; - padding: 1.25rem 1.15rem 0.25rem 1.15rem; + background-color: var(--white-100); + color: var(--black-60); + font-family: "Lato-Regular"; + font-size: 0.875rem; + letter-spacing: 0.25px; + line-height: 1.5; + padding: 1.25rem 1.15rem 0.25rem 1.15rem; } .custom_material_icons { - color: var(--black-60); - vertical-align: middle; -} \ No newline at end of file + color: var(--black-60); + vertical-align: middle; +} + +.custom_material_icons_two { + color: var(--black-60); + vertical-align: sub; +} + +.preview_image { + position: relative; + cursor: pointer; +} + +.custom_close_btn { + color: var(--status-red); + position: absolute; + text-align: center; + margin-left: -8%; + opacity: 0; + transition: 0 0.35s ease; +} + +.preview_image:hover .custom_close_btn { + opacity: 1; +} diff --git a/src/components/form-elements/InspectCheckOne.tsx b/src/components/form-elements/InspectCheckOne.tsx index a7d49005acf9eea8cb09ebc6b94975a2df3220b2..8d17106abfd0aa8953ff07c8bfacbece354870d6 100644 --- a/src/components/form-elements/InspectCheckOne.tsx +++ b/src/components/form-elements/InspectCheckOne.tsx @@ -1,6 +1,13 @@ -import { useState, useEffect } from "react"; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { useState, useEffect, useRef } from "react"; import styles from "./InspectCheckOne.module.css"; import { TextField } from "./TextField"; +import { HeadingFour } from "../headings"; +import { useRecoilState } from "recoil"; +import { dataObjectFileUpload as dataObjectFileUploadAtom } from "../../states/atoms"; +import { FormService } from "../../services"; +import { APP } from "../../constants"; +import Notify from "../../helpers/notify"; /** * InspectCheckOne component renders @@ -15,8 +22,13 @@ interface InspectCheckOneProps { modalId?: string; modalTriggerLabel?: string; clickHandler?: (event: any) => void; + attachmentRemoveHandler?: (event: any) => void; inspectionValue?: any; disableEdit: boolean; + showAttachment?: boolean; + attachments?: any; + id?: string; + showAttachmentRemover?: boolean; } export const InspectCheckOne = ({ @@ -27,17 +39,58 @@ export const InspectCheckOne = ({ modalId, modalTriggerLabel, clickHandler, + attachmentRemoveHandler, inspectionValue, disableEdit, + showAttachment, + attachments, + id, + showAttachmentRemover, }: InspectCheckOneProps) => { const [insValue, setInsValue] = useState(""); + const [dataObjectFileUploadValue, setDataObjectFileUploadValue] = + useRecoilState(dataObjectFileUploadAtom); + useEffect(() => { if (inspectionValue !== "") { setInsValue(inspectionValue); } }, [inspectionValue]); + useEffect(() => { + document + .getElementById(`${id}`) + ?.addEventListener("change", (event: any) => { + let label = event.target.id.split("attachment")[1]; + let file = event.target!.files[0]; + + let data = new FormData(); + data.append("files", file); + + FormService.uploadfile(data).then( + (response) => { + if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { + let derviedObject = { + field: label, + value: response.responseData[0], + }; + + setDataObjectFileUploadValue(derviedObject); + } else { + Notify.error(response.statusInfo.errorMessage); + } + }, + (error) => { + error.statusInfo + ? Notify.error(error.statusInfo.errorMessage) + : Notify.error(error.message); + } + ); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( <div className={`${styles.inspect_check_one} p-4`} onClick={clickHandler}> <label>{label}</label> @@ -79,6 +132,30 @@ export const InspectCheckOne = ({ ) : ( "" )} + + {showAttachment && ( + <> + <div className="float-end pt-4"> + <input + type={"file"} + id={id} + style={{ display: "none" }} + accept="image/png, image/gif, image/jpeg" + /> + <label + htmlFor={id} + className={`${styles.inspect_check_one_custom_label_one} me-4 me-sm-0 me-md-0 me-lg-4 me-xl-4`} + > + {"Attach photo"} + <span + className={`${styles.custom_material_icons_two} material-icons ps-2`} + > + add_a_photo + </span> + </label> + </div> + </> + )} </div> </div> @@ -101,6 +178,38 @@ export const InspectCheckOne = ({ </div> </div> )} + + {attachments && attachments.length > 0 && ( + <div className="mt-4"> + <HeadingFour heading="Captured/Attached photos" /> + <div className="row"> + {attachments.map((i: any, j: number) => { + return ( + <div + className={`${styles.preview_image} mt-3 col-sm-12 col-md-3 col-lg-3`} + key={j} + > + <img + src={i} + alt={`Attachment ${j}`} + title={`Attachment ${j}`} + height="100" + width="177" + /> + {showAttachmentRemover && ( + <i + className={`${styles.custom_close_btn} material-icons `} + onClick={attachmentRemoveHandler} + > + close + </i> + )} + </div> + ); + })} + </div> + </div> + )} </div> ); }; diff --git a/src/components/form-elements/SelectField.tsx b/src/components/form-elements/SelectField.tsx index 724cae07f7ee6571b1886dde204b45ae34c36799..e311509790f7ce2d6e9965c17767c96441617378 100644 --- a/src/components/form-elements/SelectField.tsx +++ b/src/components/form-elements/SelectField.tsx @@ -15,8 +15,9 @@ interface SelectFieldProps { selectId: string; changeHandler?: (event: any) => void; isReadOnly?: boolean; - value?: string; - isMultiple?: boolean + value?: any; + isMultiple?: boolean; + defaultValue?: any; } export const SelectField = ({ @@ -29,7 +30,8 @@ export const SelectField = ({ selectId, isReadOnly, value, - isMultiple + isMultiple, + defaultValue, }: SelectFieldProps) => { return ( <div className=""> @@ -39,36 +41,74 @@ export const SelectField = ({ <br /> </> )} - <select - name={selectName} - id={selectId} - className={`${styles.select_field_input}`} - value={value} - defaultValue={placeholder} - disabled={isReadOnly} - multiple={isMultiple} - onChange={changeHandler} - > - {placeholder && ( - <option - disabled - className={`${styles.select_field_input_placeholder}`} - > - {placeholder} - </option> - )} + {defaultValue !== undefined && ( + <select + name={selectName} + id={selectId} + className={`${styles.select_field_input}`} + defaultValue={defaultValue} + placeholder={placeholder} + disabled={isReadOnly} + multiple={isMultiple} + onChange={changeHandler} + > + {placeholder && ( + <option + disabled + className={`${styles.select_field_input_placeholder}`} + > + {placeholder} + </option> + )} - {option && option.map((i: any, j: any) => { - return ( + {option && + option.map((i: any, j: any) => { + return ( + <option + value={i.value} + key={j} + + > + {i.key} + </option> + ); + })} + </select> + )} + + {value !== undefined && ( + <select + name={selectName} + id={selectId} + className={`${styles.select_field_input}`} + value={value} + placeholder={placeholder} + disabled={isReadOnly} + multiple={isMultiple} + onChange={changeHandler} + > + {placeholder && ( <option - value={i.value} - key={j} + disabled + className={`${styles.select_field_input_placeholder}`} > - {i.key} + {placeholder} </option> - ); - })} - </select> + )} + + {option && + option.map((i: any, j: any) => { + return ( + <option + value={i.value} + key={j} + > + {i.key} + </option> + ); + })} + </select> + )} </div> ); }; diff --git a/src/components/form-elements/TextField.tsx b/src/components/form-elements/TextField.tsx index f5671d1543b788328a76c63488b3b493ac65e396..4d71105d490f857e9e886359d41fb93bd7414f70 100644 --- a/src/components/form-elements/TextField.tsx +++ b/src/components/form-elements/TextField.tsx @@ -1,5 +1,6 @@ import { HeadingFour } from "../headings"; import styles from "./TextField.module.css"; +import moment from "moment"; /** * TextField component renders @@ -35,6 +36,8 @@ export const TextField = ({ defaultValue, isReadOnly, }: TextFieldProps) => { + let today = new Date(); + return ( <div className={`${styles.text_field_input}`}> {showLabel && ( @@ -45,12 +48,32 @@ export const TextField = ({ )} {isReadOnly ? ( + type === "date" ? ( + <input + type={type} + placeholder={placeholder} + value={value} + min={moment(today, "DD-MM-YYYY").format("YYYY-MM-DD")} + defaultValue={defaultValue} + readOnly + /> + ) : ( + <input + type={type} + placeholder={placeholder} + value={value} + defaultValue={defaultValue} + readOnly + /> + ) + ) : type === "date" ? ( <input type={type} placeholder={placeholder} value={value} + min={moment(today, "DD-MM-YYYY").format("YYYY-MM-DD")} defaultValue={defaultValue} - readOnly + onChange={changeHandler} /> ) : ( <input diff --git a/src/components/form/FormViewer.js b/src/components/form/FormViewer.js index 2bf98d8c8395f08d822f63af03435f59cee813f8..7fed8183aff5f04b2b77f791a7fe28f1487e151f 100644 --- a/src/components/form/FormViewer.js +++ b/src/components/form/FormViewer.js @@ -219,7 +219,8 @@ class FormViewer extends Component { let inputs = document.getElementsByTagName("input"); if ( this.props.match.params.applicationId === null || - this.props.match.params.applicationId === undefined + this.props.match.params.applicationId === undefined || + this.state.applicationDetails.status === LANG.FORM_STATUS.RETURNED ) { for (let m = 0; m < inputs.length; m++) { for (var key of Object.keys(existingFields)) @@ -258,7 +259,19 @@ class FormViewer extends Component { // console.log(this.state.formFields); for (var key of Object.keys(fields)) { var element = document.getElementsByName(key); + if (element.length > 0) { + if (element[0].type === "boolean" || element[0].type === "text") { + var len = element.length; + let values = fields[key].split(","); + for (var j = 0; j < len; j++) { + // console.log(values.includes("booleanTrue")); + if (values.includes("booleanTrue")) { + element[j].parentNode.classList.add("selected"); + element[j].checked = true; + } + } + } if (element[0].type === "checkbox" || element[0].type === "radio") { var len = element.length; let values = fields[key].split(","); @@ -377,7 +390,8 @@ class FormViewer extends Component { let flag = true; if ( (this.props.match.params.applicationId === null || - this.props.match.params.applicationId === undefined) && + this.props.match.params.applicationId === undefined || + this.state.applicationDetails.status === LANG.FORM_STATUS.RETURNED) && Helper.getUserRole() === APP.ROLE.INSTITUTION ) { for (let index = 0; index <= this.state.headingIndex; index++) { @@ -414,12 +428,14 @@ class FormViewer extends Component { // console.log("saveFields..."); if ( this.props.match.params.applicationId === null || - this.props.match.params.applicationId === undefined + this.props.match.params.applicationId === undefined || + this.state.applicationDetails.status === LANG.FORM_STATUS.RETURNED ) { let obj = this.state.formFields, order = ""; var form = document.getElementById("application-form"); const formData = new FormData(form); + const data = Array.from(formData.entries()).reduce( (memo, pair) => ({ ...memo, @@ -427,10 +443,29 @@ class FormViewer extends Component { }), {} ); + for (let i = 0; i < this.state.formFieldGroups[index].length; i++) { order = this.state.formFieldGroups[index][i]["order"]; obj["field_" + order] = data["field_" + order] !== undefined ? data["field_" + order] : ""; + + var booleans = document.getElementsByClassName( + "field_" + order + "_boolean" + ); + + if (booleans.length) { + if (booleans[0].type === "checkbox") { + var len = booleans.length; + let temp = []; + for (var j = 0; j < len; j++) { + if (booleans[j].parentElement.classList.contains("selected")) { + temp.push((booleans[j].value = "booleanTrue")); + } + } + obj["field_" + order] = temp.join(); + } + } + var checkboxes = document.getElementsByClassName( "field_" + order + "_checkbox" ); @@ -465,6 +500,7 @@ class FormViewer extends Component { obj["field_" + order] = files[0].getAttribute("path"); } } + this.setState({ formFields: obj, }); @@ -479,6 +515,7 @@ class FormViewer extends Component { temp; var savedFields = this.state.formFields; var fields = this.state.formDetails.fields; + for (const key in savedFields) { temp = key.split("_"); for (let j = 0; j < fields.length; j++) { @@ -488,14 +525,17 @@ class FormViewer extends Component { } } } + var fieldGroups = {}; for (let i = 0; i < this.state.formHeadings.length; i++) { fieldGroups[this.state.formHeadings[i]] = {}; for (let j = 0; j < this.state.formFieldGroups[i].length; j++) { // console.log(this.state.formFieldGroups[i][j].name); - fieldGroups[this.state.formHeadings[i]][ - this.state.formFieldGroups[i][j].name - ] = fieldsData[this.state.formFieldGroups[i][j].name]; + if (fieldsData[this.state.formFieldGroups[i][j].name] !== "") { + fieldGroups[this.state.formHeadings[i]][ + this.state.formFieldGroups[i][j].name + ] = fieldsData[this.state.formFieldGroups[i][j].name]; + } } } // console.log(fieldGroups); @@ -509,8 +549,10 @@ class FormViewer extends Component { applicationId: this.props.match.params.applicationId, }), }; + // formDetails = JSON.stringify(formDetails); - // console.log(formDetails) + // console.log(JSON.parse(formDetails)); + FormService.submit(formDetails).then( (response) => { // console.log(response); @@ -631,8 +673,8 @@ class FormViewer extends Component { : "" } comments={ - this.state.applicationDetails.comments - ? this.state.applicationDetails.comments + this.state.applicationDetails.notes + ? this.state.applicationDetails.notes : "" } showInspectionDetails={true} diff --git a/src/components/form/ListForms.js b/src/components/form/ListForms.js index afd1d1733c298d14d27f4cd9fed58c5cd5064845..4cbd06aee888dfb04c7e4516acc16704feae24b6 100644 --- a/src/components/form/ListForms.js +++ b/src/components/form/ListForms.js @@ -36,7 +36,7 @@ class ListForms extends Component { : Notify.error(error.message); } ); - } + }; getFormShortCode = (name) => { let shortCode; @@ -57,10 +57,10 @@ class ListForms extends Component { formData.id = form.id; formData.version = form.version; formData.title = form.title; - if(isPublish) { - formData.status = LANG.FORM_STATUS.PUBLISH + if (isPublish) { + formData.status = LANG.FORM_STATUS.PUBLISH; } else { - formData.status = LANG.FORM_STATUS.UNPUBLISH + formData.status = LANG.FORM_STATUS.UNPUBLISH; } FormService.add(formData).then( (response) => { @@ -80,7 +80,7 @@ class ListForms extends Component { : Notify.error(error.message); } ); - } + }; searchForms = (event) => { var input, filter, formContainer, formItems, a, i, txtValue; @@ -115,14 +115,14 @@ class ListForms extends Component { <input type="text" className="form-control" - id="search-roles" + id="searchRoles" placeholder="Search for an application" autoComplete="off" onKeyUp={(event) => this.searchForms(event)} /> </div> </form> - + {/* <div className="col-md-8"> <Link to="/forms/add" className="pull-right"> <button className="btn btn-default smf-btn-default-inverse"> @@ -132,7 +132,14 @@ class ListForms extends Component { </div> */} </div> <div className="col-sm-12 col-md-8 text-right"> - <BtnTwo btnType="button" label="Create new" isLink={true} link={`/forms/add`} floatBottom={false} isModal={false} /> + <BtnTwo + btnType="button" + label="Create new form" + isLink={true} + link={`/forms/add`} + floatBottom={false} + isModal={false} + /> </div> </div> <div className="row pt2"> @@ -158,15 +165,24 @@ class ListForms extends Component { {form.status.toLowerCase()} </td> <td className=""> - { - form.status === LANG.FORM_STATUS.PUBLISH && + {form.status === LANG.FORM_STATUS.PUBLISH && ( // <button type="button" className="btn btn-link td-preview">Unpublish</button> - <span className="d-inline-block td-preview cursor-pointer" onClick={(e) => this.submit(form, false)}>Unpublish</span> - } - { - (form.status === LANG.FORM_STATUS.NEW || form.status === LANG.FORM_STATUS.UNPUBLISH) && - <span className="d-inline-block td-preview cursor-pointer" onClick={(e) => this.submit(form, true)}>publish</span> - } + <span + className="d-inline-block td-preview cursor-pointer" + onClick={(e) => this.submit(form, false)} + > + Unpublish + </span> + )} + {(form.status === LANG.FORM_STATUS.NEW || + form.status === LANG.FORM_STATUS.UNPUBLISH) && ( + <span + className="d-inline-block td-preview cursor-pointer" + onClick={(e) => this.submit(form, true)} + > + publish + </span> + )} {/* { form.status === LANG.FORM_STATUS.DRAFT && <span className="font-weight-bold black-60">Draft</span> @@ -177,14 +193,12 @@ class ListForms extends Component { <Link to={`/forms/${form.id}`}>Preview</Link> </td> <td className="td-preview"> - { - form.status === LANG.FORM_STATUS.DRAFT && + {form.status === LANG.FORM_STATUS.DRAFT && ( <Link to={`/forms/${form.id}/edit`}>Edit</Link> - } - { - form.status !== LANG.FORM_STATUS.DRAFT && + )} + {form.status !== LANG.FORM_STATUS.DRAFT && ( <span className="font-weight-bold black-16">Edit</span> - } + )} </td> </tr> ))} diff --git a/src/components/form/fields/Checkbox.js b/src/components/form/fields/Checkbox.js index e663d0cc4bdad2ee1296abfd99895c5f3d25687a..2de562d7afb1488591c02bc65fdb1146e329cc2b 100644 --- a/src/components/form/fields/Checkbox.js +++ b/src/components/form/fields/Checkbox.js @@ -42,6 +42,7 @@ class Checkbox extends Component { </span> )} </label> + {this.props.field.values.map((option, key) => ( <div className="custom-form-check form-check" key={key}> <label diff --git a/src/components/form/fields/Input.js b/src/components/form/fields/Input.js index 9b30caead12c8db1982781a5e507bd25110c3cdd..d7af090ea3c14a7402b25cca67949e0beebc395c 100644 --- a/src/components/form/fields/Input.js +++ b/src/components/form/fields/Input.js @@ -12,7 +12,9 @@ class Input extends Component { this.state = { fieldType: "", language: "en", + isInvalid: false, }; + this.validateEmailInput = this.validateEmailInput.bind(this); } componentDidMount() { @@ -34,6 +36,33 @@ class Input extends Component { } } + validateEmailInput = (e) => { + let inputData = e.target.value; + //eslint-disable-next-line + let allowedFormat = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/; + if (inputData.match(allowedFormat)) { + this.setState({ + isInvalid: false, + }); + } else { + this.setState({ + isInvalid: true, + }); + } + }; + + componentDidUpdate() { + if (this.props.field.fieldType === LANG.FIELD_TYPES.numeric.toLowerCase()) { + setTimeout(() => { + let numericField = document.getElementById( + `field-${this.props.field.order}` + ); + + numericField.type = "number"; + }, 250); + } + } + render() { // console.log(this.props.field.fieldType); // strings.setLanguage( @@ -54,19 +83,41 @@ class Input extends Component { </span> )} </label> - <input - type={ - this.props.field.fieldType === - LANG.FIELD_TYPES.numeric.toLowerCase() - ? "number" - : this.state.fieldType - } - id={"field-" + this.props.field.order} - name={"field_" + this.props.field.order} - className="form-control" - placeholder="Type here" - autoComplete="off" - /> + + {this.props.field.fieldType !== + LANG.FIELD_TYPES.email.toLowerCase() && ( + <input + type={ + this.props.field.fieldType === + LANG.FIELD_TYPES.numeric.toLowerCase() + ? "number" + : this.state.fieldType + } + id={"field-" + this.props.field.order} + name={"field_" + this.props.field.order} + className="form-control" + placeholder="Type here" + autoComplete="off" + /> + )} + + {this.props.field.fieldType === + LANG.FIELD_TYPES.email.toLowerCase() && ( + <div className=""> + <input + type="email" + id={"field-" + this.props.field.order} + name={"field_" + this.props.field.order} + className="form-control" + placeholder="Type here" + autoComplete="off" + onChange={this.validateEmailInput} + /> + {this.state.isInvalid && ( + <p className="invalid-input">Invalid email!</p> + )} + </div> + )} </div> </div> ); diff --git a/src/components/form/fields/Toggle.js b/src/components/form/fields/Toggle.js index 768109f4a264d20a9ec8de508d2839a32dfddfad..0af35dcbc17e1be98a2f42c8e278699e1120dd50 100644 --- a/src/components/form/fields/Toggle.js +++ b/src/components/form/fields/Toggle.js @@ -16,6 +16,30 @@ class Toggle extends Component { handleChange = (event) => {}; + componentDidMount() { + if (this.props.field.fieldType === LANG.FIELD_TYPES.boolean.toLowerCase()) { + setTimeout(() => { + let booleanField = document.getElementById( + `field-${this.props.field.order}` + ); + + booleanField.type = "checkbox"; + }, 250); + } + } + + componentDidUpdate() { + if (this.props.field.fieldType === LANG.FIELD_TYPES.boolean.toLowerCase()) { + setTimeout(() => { + let booleanField = document.getElementById( + `field-${this.props.field.order}` + ); + + booleanField.type = "checkbox"; + }, 250); + } + } + render() { // strings.setLanguage( // localStorage.getItem("language") || this.state.language @@ -36,11 +60,17 @@ class Toggle extends Component { )} </label> <br /> + <label className="switch"> <input type="checkbox" id={"field-" + this.props.field.order} name={"field_" + this.props.field.order} + className={ + "mr-2 field_" + + this.props.field.order + + "_boolean" + } /> <span className="slider round"></span> </label> diff --git a/src/components/modal/InspectionScheduleModal.tsx b/src/components/modal/InspectionScheduleModal.tsx index 868c101c30c2c9f780f8d0b40c21e2063d31d5c0..9394dd69522b1d9768c3ce47b40fbc80aee9c406 100644 --- a/src/components/modal/InspectionScheduleModal.tsx +++ b/src/components/modal/InspectionScheduleModal.tsx @@ -40,14 +40,26 @@ export const InspectionScheduleModal = ({ }: InspectionScheduleModalProps) => { const [date, setDate] = useState(""); const [inspectorsList, setInspectorsList] = useState<any[]>([]); + const [curatedInspectorsListOne, setCuratedInspectorsListOne] = useState< + any[] + >([]); + const [curatedInspectorsListTwo, setCuratedInspectorsListTwo] = useState< + any[] + >([]); const [leadInspectors, setLeadInspectors] = useState<any[]>([]); const [curatedLeadInspectors, setCuratedLeadInspectors] = useState<any[]>([]); const [curatedAssitingInspectors, setCuratedAssitingInspectors] = useState< any[] >([]); const [currentLeadIns, setCurrentLeadIns] = useState(""); + // const [currentLeadInsName, setCurrentLeadInsName] = useState( + // "Select from the list" + // ); const [assitingInspectors, setAssitingInspectors] = useState<any[]>([]); const [currentAssitingIns, setCurrentAssitingIns] = useState(""); + // const [currentAssitingInsName, setCurrentAssitingInsName] = useState( + // "Select from the list" + // ); const [disableSubmit, setDisableSubmit] = useState(true); let history = useHistory(); @@ -61,7 +73,7 @@ export const InspectionScheduleModal = ({ response.responseData.map((i: any, j: number) => { return tempArray.push({ value: i.id, - key: i.firstName, + key: i.firstName + " " + i.lastName, logo: i.firstName[0] + i.lastName[0], }); }); @@ -87,13 +99,13 @@ export const InspectionScheduleModal = ({ inspectionData.assignedTo.map((i: any, j: number) => { if (i.leadInspector) { return (curatedObject = { - key: i.firstName, + key: i.firstName + " " + i.lastName, value: i.id, logo: i.firstName[0] + i.lastName[0], }); } else { return curatedArray.push({ - key: i.firstName, + key: i.firstName + " " + i.lastName, value: i.id, logo: i.firstName[0] + i.lastName[0], }); @@ -147,6 +159,21 @@ export const InspectionScheduleModal = ({ setLeadInspectors(removeDup); + let tempInsArray = [...inspectorsList]; + + tempInsArray.map((k, l) => { + if (k.value === parseInt(currentLeadIns)) { + let index = tempInsArray.indexOf(k); + if (index > -1) { + tempInsArray.splice(index, 1); + } + return null; + } + return null; + }); + + setCuratedInspectorsListTwo(tempInsArray); + setCuratedLeadInspectors([curatedArray]); } }; @@ -167,6 +194,21 @@ export const InspectionScheduleModal = ({ setLeadInspectors(tempArrayTwo); + let tempInsArray = [...inspectorsList]; + + tempInsArray.map((k, l) => { + if (k.value === parseInt(currentLeadIns)) { + let index = tempInsArray.indexOf(k); + if (index > -1) { + tempInsArray.splice(index, 1); + } + return null; + } + return null; + }); + + setCuratedInspectorsListTwo(tempInsArray); + setCuratedLeadInspectors(tempArray); }; @@ -197,6 +239,24 @@ export const InspectionScheduleModal = ({ setAssitingInspectors(removeDup); + let tempInsArray = [...inspectorsList]; + + curatedArray.map((m: any, n: number) => { + tempInsArray.map((k, l) => { + let index = tempInsArray.indexOf(k); + if (k.value === m.value) { + if (index > -1) { + tempInsArray.splice(index, 1); + } + return null; + } + return null; + }); + return null; + }); + + setCuratedInspectorsListOne(tempInsArray); + setCuratedAssitingInspectors(curatedArray); } }; @@ -217,6 +277,24 @@ export const InspectionScheduleModal = ({ setAssitingInspectors(tempArrayTwo); + let tempInsArray = [...inspectorsList]; + + tempArray.map((m: any, n: number) => { + tempInsArray.map((k, l) => { + let index = tempInsArray.indexOf(k); + if (k.value === m.value) { + if (index > -1) { + tempInsArray.splice(index, 1); + } + return null; + } + return null; + }); + return null; + }); + + setCuratedInspectorsListOne(tempInsArray); + setCuratedAssitingInspectors(tempArray); }; @@ -261,6 +339,24 @@ export const InspectionScheduleModal = ({ ); }; + // const getName = (value: any, type: string) => { + // if (type === "assistingInspector") { + // inspectorsList.map((k, l) => { + // if (k.value === parseInt(value)) { + // setCurrentAssitingInsName(k.key); + // } + // }); + // } + + // if (type === "leadInspector") { + // inspectorsList.map((k, l) => { + // if (k.value === parseInt(value)) { + // setCurrentLeadInsName(k.key); + // } + // }); + // } + // }; + useEffect(() => { if (curatedAssitingInspectors.length === 0) { setDisableSubmit(true); @@ -308,25 +404,44 @@ export const InspectionScheduleModal = ({ </div> <div className="row"> <div className="col-9"> + <SelectField showLabel={false} - option={inspectorsList} + option={ + curatedInspectorsListOne.length > 0 + ? curatedInspectorsListOne + : inspectorsList + } selectId="leadInspector" selectName="leadInspector" placeholder="Select from the list" + value={ + currentLeadIns.length ? currentLeadIns : "Select from the list" + } changeHandler={(e) => { setCurrentLeadIns(e.target.value); + // getName(e.target.value, "leadInspector"); }} /> </div> <div className="col-2"> - <BtnOne - label="Add" - btnType="button" - isLink={false} - link="" - clickHandler={(e) => addLeadInspectors(e)} - /> + {currentLeadIns !== "" ? ( + <BtnOne + label="Add" + btnType="button" + isLink={false} + link="" + clickHandler={(e) => addLeadInspectors(e)} + /> + ) : ( + <button + type="button" + className={`${btnStyle.btn_one_disabled}`} + disabled={true} + > + Add + </button> + )} </div> </div> <div className="pt-2"> @@ -369,25 +484,44 @@ export const InspectionScheduleModal = ({ </div> <div className="row"> <div className="col-sm-12 col-md-9 col-lg-9"> + <SelectField showLabel={false} - option={inspectorsList} + option={ + curatedInspectorsListTwo.length > 0 + ? curatedInspectorsListTwo + : inspectorsList + } selectId="assistingInspectors" selectName="assistingInspectors" placeholder="Select from the list" + value={ + currentAssitingIns.length ? currentAssitingIns : "Select from the list" + } changeHandler={(e) => { setCurrentAssitingIns(e.target.value); + // getName(e.target.value, "assistingInspector"); }} /> </div> <div className="col-sm-12 col-md-2 col-lg-2"> - <BtnOne - label="Add" - btnType="button" - isLink={false} - link="" - clickHandler={(e) => addAssitingInspectors(e)} - /> + {currentAssitingIns !== "" ? ( + <BtnOne + label="Add" + btnType="button" + isLink={false} + link="" + clickHandler={(e) => addAssitingInspectors(e)} + /> + ) : ( + <button + type="button" + className={`${btnStyle.btn_one_disabled}`} + disabled={true} + > + Add + </button> + )} </div> <div className="pt-2"> {curatedAssitingInspectors && @@ -464,6 +598,9 @@ export const InspectionScheduleModal = ({ type="button" className={`${btnStyle.btn_one} me-2`} data-dismiss="modal" + onClick={(e) => { + e.preventDefault(); + }} > Cancel </button> diff --git a/src/components/modal/ModalTwo.tsx b/src/components/modal/ModalTwo.tsx index 31c1643ac3416f34dc00080407f9207ce5be1ce4..62d4a1165cbdb249d023b2c9a093f3b8663b9233 100644 --- a/src/components/modal/ModalTwo.tsx +++ b/src/components/modal/ModalTwo.tsx @@ -152,15 +152,19 @@ export const ModalTwo = ({ </div> )} </div> + <div className={`${styles.custom_modal_footer} modal-footer p-0 m-0 pt-3 pb-3`} > <div className="col-6 m-0"> - {!enableHandler ? ( + {!cancelHandler ? ( <button type="button" className={`${btnStyle.btn_one} me-2`} data-dismiss="modal" + onClick={() => { + setNote(""); + }} > Cancel </button> @@ -213,7 +217,7 @@ export const ModalTwo = ({ ) : ( <button type="button" - className={`${btnStyleTwo.btn_two}`} + className={`${btnStyleTwo.btn_two_disabled}`} disabled={true} > Submit diff --git a/src/components/status-bar/StatusBarLarge.tsx b/src/components/status-bar/StatusBarLarge.tsx index c986433304305ec31d18b170b6049b2476a57b2e..aeb623fcbe961d1cb906146bf38f1914bb0d11d2 100644 --- a/src/components/status-bar/StatusBarLarge.tsx +++ b/src/components/status-bar/StatusBarLarge.tsx @@ -119,14 +119,15 @@ export const StatusBarLarge = ({ <div className="pt-1 pb-4 mx-3"> <div className="row"> <div className="col-12"> - {comments && + <p className={`${styles.review_comment} p-2 `}>{comments}</p> + {/* {comments && comments.map((c: any, i: any) => { return ( <p key={i} className={`${styles.review_comment} p-2 `}> {c.value} </p> ); - })} + })} */} </div> </div> </div> @@ -199,7 +200,9 @@ export const StatusBarLarge = ({ > {k.firstName[0] + k.lastName[0]} </div> - <p className="ps-2">{k.firstName}</p> + <p className="ps-2"> + {k.firstName + " " + k.lastName} + </p> </div> </div> </div> @@ -229,7 +232,9 @@ export const StatusBarLarge = ({ > {k.firstName[0] + k.lastName[0]} </div> - <p className="ps-2">{k.firstName}</p> + <p className="ps-2"> + {k.firstName + " " + k.lastName} + </p> </div> </div> </div> diff --git a/src/constants/ApiConstants.ts b/src/constants/ApiConstants.ts index 0dfc07744aaeca45db4d66558cca97fefd2dbb91..80e50ca6883a1d5b51a3fa66206d8a4566ee043b 100644 --- a/src/constants/ApiConstants.ts +++ b/src/constants/ApiConstants.ts @@ -33,4 +33,9 @@ export const APIS = { GET_USER_BY_ID: "user/getUserById", GET_ALL_USERS: "user/v1/getAllUser", }, + DASHBOARD: { + GET_DASHBOARD_CONFIG: "dashboard/getDashboardConfig/SMF/home", + GET_DASHBOARD_PROFILE: "dashboard/getDashboardsForProfile/SMF", + GET_CHART_DATA: "dashboard/getChartV2/SMF", + }, }; diff --git a/src/helpers/exportChart.js b/src/helpers/exportChart.js new file mode 100644 index 0000000000000000000000000000000000000000..b6f9c21dbd31af39e77cb2ad71e4a6ed2b17a452 --- /dev/null +++ b/src/helpers/exportChart.js @@ -0,0 +1,103 @@ +import NFormatterFun from "../components/charts/NFormatterFun"; +const ExportChart = { + _charts: {}, + + setAttribute(key, value) { + this._charts[key] = value; + }, + + getRows(chartCode) { + var rows = []; + var chartData = this._charts[chartCode][0].plots; + var tempVal; + chartData.forEach((d1, i) => { + if (i === 0) { + if (d1.parentLabel !== null && d1.parentLabel !== "") { + rows.push([d1.label, d1.valueLabel, d1.parentLabel]); + } else { + rows.push([d1.label, d1.valueLabel]); + } + } + tempVal = NFormatterFun(d1.value, d1.symbol, "Unit"); + tempVal = + typeof tempVal == "string" + ? parseFloat(tempVal.replace(/,/g, "")) + : tempVal; + if (d1.parentName !== null && d1.parentName !== "") { + rows.push([d1.name, tempVal, d1.parentName]); + } else { + rows.push([d1.name, tempVal]); + } + }); + + return rows; + }, + + tableToCsv(filename) { + var tableParentId = "#" + filename.replace(/\s/g, ""); + var csv = []; + var rows = document.querySelectorAll(tableParentId + " table tr"); + if (rows.length === 0 && rows !== undefined) { + rows = document.querySelectorAll("#modalView table tr"); + } + for (var i = 0; i < rows.length; i++) { + var row = [], + cols = rows[i].querySelectorAll("td, th"); + + for (var j = 0; j < cols.length; j++) + row.push(cols[j].innerText.replace(/,/g, "")); + + csv.push(row.join(",")); + } + + // Download CSV file + this.downloadCSV(csv.join("\n"), filename); + }, + + toCsv(filename, chartCode) { + var rows = this.getRows(chartCode); + var processRow = function (row) { + var finalVal = ""; + for (var j = 0; j < row.length; j++) { + var innerValue = row[j] === null ? "" : row[j].toString(); + if (row[j] instanceof Date) { + innerValue = row[j].toLocaleString(); + } + var result = innerValue.replace(/"/g, '""'); + if (result.search(/("|,|\n)/g) >= 0) result = '"' + result + '"'; + if (j > 0) finalVal += ","; + finalVal += result; + } + return finalVal + "\n"; + }; + + var csvFile = ""; + for (var i = 0; i < rows.length; i++) { + csvFile += processRow(rows[i]); + } + this.downloadCSV(csvFile, filename); + }, + + downloadCSV(csvFile, filename) { + var blob = new Blob([csvFile], { type: "text/csv;charset=utf-8;" }); + if (navigator.msSaveBlob) { + // IE 10+ + navigator.msSaveBlob(blob, filename); + } else { + var link = document.createElement("a"); + if (link.download !== undefined) { + // feature detection + // Browsers that support HTML5 download attribute + var url = URL.createObjectURL(blob); + link.setAttribute("href", url); + link.setAttribute("download", filename + ".csv"); + link.style.visibility = "hidden"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + } + } + }, +}; + +export default ExportChart; diff --git a/src/index.tsx b/src/index.tsx index bdacd3935c16656dcde08f021127974067fdc332..3b91690a14ec1f22f440c1fd7b64490c4ec2c8f4 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import 'bootstrap/dist/css/bootstrap.css'; +import 'bootstrap/dist/js/bootstrap.js'; import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; diff --git a/src/layouts/Dashboard/DashboardLayout.tsx b/src/layouts/Dashboard/DashboardLayout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..cbefac85a582acd5b947a0413a2eaaf42947630d --- /dev/null +++ b/src/layouts/Dashboard/DashboardLayout.tsx @@ -0,0 +1,56 @@ +/*eslint-disable no-empty-pattern*/ +// @ts-nocheck +import _ from "lodash"; +import PageLayout from "./PageLayout"; +import { useHistory } from "react-router-dom"; +import WidgetNavBar from "../../components/charts/WidgetNavBar"; + +/** + * DashboardLayout component renders + * the visualisations based on the configurations + * for regulator + */ + +interface DashboardLayoutProps { + dashboardConfig: any; +} + +export const DashboardLayout = ({ dashboardConfig }: DashboardLayoutProps) => { + let history = useHistory(); + + const renderCharts = () => { + let dashboardConfigData: any = dashboardConfig; + + let tabsInitData = _.chain(dashboardConfigData) + .first() + .get("visualizations") + .groupBy("name") + .value(); + + return ( + <div> + {_.map(tabsInitData, (k, v) => { + return ( + <PageLayout + key={v} + chartRowData={k} + row={k.row} + pathName={history.location.pathname} + /> + ); + })} + </div> + ); + }; + + return ( + <div className=""> + <div className=""> + <WidgetNavBar history={history}/> + </div> + <div className="row pt-2"> + {dashboardConfig.length > 0 && renderCharts()} + </div> + </div> + ); +}; diff --git a/src/layouts/Dashboard/PageLayout.js b/src/layouts/Dashboard/PageLayout.js new file mode 100644 index 0000000000000000000000000000000000000000..0ece9fd561a9f3b6256882bf3a2491349db7aca1 --- /dev/null +++ b/src/layouts/Dashboard/PageLayout.js @@ -0,0 +1,34 @@ +import React, { Component } from "react"; +import GenericCharts from "../../components/charts/GenericCharts"; + +/** + * Page layout to display the charts which are generated dynamically + */ + +class PageLayout extends Component { + constructor(props) { + super(props); + this.state = { + data: null, + }; + } + + render() { + let { chartRowData, row } = this.props; + + return ( + <div key={`generic${row}`}> + {chartRowData.map((vizData, j) => ( + <GenericCharts + key={j} + row={row} + chartData={vizData} + pathProps={this.props.pathName} + /> + ))} + </div> + ); + } +} + +export default PageLayout; diff --git a/src/layouts/Inspector/ConsentFormView.tsx b/src/layouts/Inspector/ConsentFormView.tsx index 24455a1024a5c1d10795c74082bdfa06cecebf42..136b347c8e7995e989e7ae28fbb9b23b98a41ac2 100644 --- a/src/layouts/Inspector/ConsentFormView.tsx +++ b/src/layouts/Inspector/ConsentFormView.tsx @@ -141,6 +141,7 @@ export const ConsentFormView = ({ isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); } return null; @@ -182,11 +183,12 @@ export const ConsentFormView = ({ sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: k.attachments, }); } else { return tempFormArray.push({ @@ -195,7 +197,7 @@ export const ConsentFormView = ({ sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: tempArrayTwo[n].fields[k.label]["value"] === "correct" @@ -204,6 +206,7 @@ export const ConsentFormView = ({ inspectionValue: tempArrayTwo[n].fields[k.label]["inspectionValue"], comments: tempArrayTwo[n].fields[k.label]["comments"], + attachments: tempArrayTwo[n].fields[k.label]["attachments"], }); } }); @@ -221,11 +224,12 @@ export const ConsentFormView = ({ sideMenu: i.sideMenu, label: m.name, value: i.fields[m.name], - defaultValues: m.values, + defaultValues: m.defaultValues, fieldType: m.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: m.attachments, }); }); return null; @@ -655,7 +659,7 @@ export const ConsentFormView = ({ : "" } comments={ - applicationData.comments ? applicationData.comments : "" + applicationData.notes ? applicationData.notes : "" } approvedNote={ applicationData.status !== LANG.FORM_STATUS.RETURNED && @@ -715,7 +719,9 @@ export const ConsentFormView = ({ k.lastName[0]} </div> <p className="ps-2"> - {k.firstName} + {k.firstName + + " " + + k.lastName} </p> </div> </div> @@ -753,7 +759,9 @@ export const ConsentFormView = ({ k.lastName[0]} </div> <p className="ps-2"> - {k.firstName} + {k.firstName + + " " + + k.lastName} </p> </div> {k.status && ( @@ -848,10 +856,101 @@ 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"> + {k.isCorrect === "" ? ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={false} + /> + </div> + </> + ) : k.isCorrect === true ? ( + <> + <div className="me-3"> + <Radio + isSelected={true} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={false} + /> + </div> + </> + ) : ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={true} + label="Incorrect" + isModal={false} + /> + </div> + </> + )} + </div> + } + showComments={ + k.comments !== "" ? true : false + } + comments={k.comments} + /> + </div> + </> + } + /> + </div> + ); + case "email": + return ( + <div className="mt-3" key={l}> + <CardThree + children={ + <> + <div className="ps-4 pe-4 pt-3 col-4"> + <TextField + showLabel={k.label ? true : false} + label={k.label || ""} + type="email" + isReadOnly={true} + value={k.value || ""} + /> + </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"> {k.isCorrect === "" ? ( @@ -937,6 +1036,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1015,7 +1116,7 @@ export const ConsentFormView = ({ isReadOnly={true} label={k.label || ""} option={k.defaultValues} - placeholder={k.value || ""} + defaultValue={k.value || ""} /> </div> @@ -1024,6 +1125,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1127,6 +1230,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1211,6 +1316,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1296,6 +1403,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1374,7 +1483,7 @@ export const ConsentFormView = ({ isReadOnly={true} label={k.label || ""} option={k.defaultValues} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} isMultiple={true} /> </div> @@ -1384,6 +1493,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1459,7 +1570,7 @@ export const ConsentFormView = ({ <CheckBoxField label={k.label || ""} showLabel={false} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} defaultValues={k.defaultValues} /> </div> @@ -1469,6 +1580,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1543,7 +1656,7 @@ export const ConsentFormView = ({ <FileUploadView showLabel={k.label ? true : false} label={k.label || ""} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} /> </div> @@ -1552,6 +1665,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1626,6 +1741,7 @@ export const ConsentFormView = ({ <BooleanField showLabel={k.label ? true : false} label={k.label || ""} + value={k.value} isReadOnly={true} /> </div> @@ -1634,6 +1750,8 @@ export const ConsentFormView = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( diff --git a/src/layouts/Inspector/FormView.tsx b/src/layouts/Inspector/FormView.tsx index 8abf1136857604fffb5020a39c7f78dcb1e3d8c8..0b176f534e3919e8756333d3167dd41f0998af9a 100644 --- a/src/layouts/Inspector/FormView.tsx +++ b/src/layouts/Inspector/FormView.tsx @@ -23,6 +23,7 @@ import { modalTwoTextArea as modalTwoTextAreaAtom, modalTwoInspectionValue as modalTwoInspectionValueAtom, dataObjectInspectionForm as dataObjectInspectionFormAtom, + dataObjectFileUpload as dataObjectFileUploadAtom, } from "../../states/atoms"; import { useHistory } from "react-router-dom"; @@ -53,8 +54,12 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { dataObjectInspectionFormAtom ); + const dataObjectAttachment = useRecoilState(dataObjectFileUploadAtom); + let history = useHistory(); + const [file, setFile] = useState({}); + const [processedData, setProcessedData] = useState<any[]>([]); const updateMenuSelection = (e: any, value: string) => { @@ -128,6 +133,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); } return null; @@ -169,11 +175,12 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); } else { return tempFormArray.push({ @@ -182,7 +189,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: tempArrayTwo[n].fields[k.label]["value"] === "correct" @@ -191,6 +198,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { inspectionValue: tempArrayTwo[n].fields[k.label]["inspectionValue"], comments: tempArrayTwo[n].fields[k.label]["comments"], + attachments: [], }); } }); @@ -208,11 +216,12 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { sideMenu: i.sideMenu, label: m.name, value: i.fields[m.name], - defaultValues: m.values, + defaultValues: m.defaultValues, fieldType: m.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); }); return null; @@ -251,7 +260,6 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { setProcessedData(tempArray); } } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [applicationData]); @@ -291,8 +299,10 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { setModalInspectionValue(m.inspectionValue); if (status === "correct") { m.isCorrect = true; - m.comments = m.comments; - m.inspectionValue = m.inspectionValue; + m.comments = ""; + m.inspectionValue = ""; + setModalTextArea(""); + setModalInspectionValue(""); } else if (status === "incorrect") { m.isCorrect = false; m.comments = m.comments; @@ -377,6 +387,28 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedMenuLabel, processedData]); + useEffect(() => { + if (Object.keys(dataObjectAttachment[0]).length > 0) { + let label = Object.values(dataObjectAttachment[0])[0]; + let file = Object.values(dataObjectAttachment[0])[1]; + + let tempArray = [...processedData]; + tempArray.map((i, j) => { + i.fields && + i.fields.map((m: any, n: number) => { + if (m.label === label) { + return m.attachments.push(file); + } + return null; + }); + return null; + }); + setProcessedData(tempArray); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dataObjectAttachment[0]]); + const processFormData = (e: any) => { e.preventDefault(); @@ -390,6 +422,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { [m.label]: { value: m.isCorrect ? "correct" : "incorrect", comments: m.comments, + attachments: m.attachments, inspectionValue: m.inspectionValue, }, ...dataObject[i.sideMenu], @@ -407,6 +440,27 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { setDataObjectFormValue(dataObject); }; + const removeAttachment = (e: any, id: any) => { + e.preventDefault(); + + let fileName = e.target.parentElement.children[0].currentSrc; + + let tempArray = [...processedData]; + tempArray.map((i, j) => { + i.fields && + i.fields.map((m: any, n: number) => { + if (m.label === id) { + let index = m.attachments.indexOf(fileName); + if (index > -1) { + m.attachments.splice(index, 1); + } + } + return null; + }); + return null; + }); + setProcessedData(tempArray); + }; return ( <div className=""> {applicationData && ( @@ -476,6 +530,180 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + showAttachment={true} + id={`attachment${k.label}`} + attachments={k.attachments} + inspectionValue={k.inspectionValue} + children={ + <div className="d-flex flex-row"> + {k.isCorrect === "" ? ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + clickHandler={(e) => { + onCheckCorrectness( + e, + selectedMenuLabel, + k.label, + "correct" + ); + }} + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={true} + modalId={ + k.label + .replace(/\s/g, "") + .replace( + /[^a-zA-Z ]/g, + "" + ) + k.id + } + clickHandler={(e) => { + onCheckCorrectness( + e, + selectedMenuLabel, + k.label, + "incorrect" + ); + }} + /> + </div> + </> + ) : k.isCorrect === true ? ( + <> + <div className="me-3"> + <Radio + isSelected={true} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={true} + modalId={ + k.label + .replace(/\s/g, "") + .replace( + /[^a-zA-Z ]/g, + "" + ) + k.id + } + clickHandler={(e) => { + onCheckCorrectness( + e, + selectedMenuLabel, + k.label, + "incorrect" + ); + }} + /> + </div> + </> + ) : ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + clickHandler={(e) => { + onCheckCorrectness( + e, + selectedMenuLabel, + k.label, + "correct" + ); + }} + /> + </div> + <div className="me-3"> + <Radio + isSelected={true} + label="Incorrect" + isModal={true} + modalId={ + k.label + .replace(/\s/g, "") + .replace( + /[^a-zA-Z ]/g, + "" + ) + k.id + } + clickHandler={(e) => { + onCheckCorrectness( + e, + selectedMenuLabel, + k.label, + "incorrect" + ); + }} + /> + </div> + </> + )} + </div> + } + showComments={ + k.comments !== "" ? true : false + } + comments={k.comments} + modalId={ + k.label + .replace(/\s/g, "") + .replace(/[^a-zA-Z ]/g, "") + k.id + } + /> + </div> + </> + } + /> + </div> + ); + case "email": + return ( + <div className="mt-3" key={l}> + <CardThree + children={ + <> + <div className="ps-4 pe-4 pt-3 col-4"> + <TextField + showLabel={k.label ? true : false} + label={k.label || ""} + type="email" + isReadOnly={true} + value={k.value || ""} + /> + </div> + <div className="mt-3"> + <InspectCheckOne + label="Is the given information found correct?" + modalTriggerLabel={"Edit"} + disableEdit={false} + showAttachment={true} + attachments={k.attachments} + clickHandler={(e) => { + setModalTextArea(k.comments); + setModalInspectionValue( + k.inspectionValue + ); + }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -630,12 +858,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -784,7 +1019,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { isReadOnly={true} label={k.label || ""} option={k.defaultValues} - placeholder={k.value || ""} + defaultValue={k.value || ""} /> </div> <div className="mt-3"> @@ -792,12 +1027,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -970,12 +1212,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1129,12 +1378,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1289,12 +1545,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1443,7 +1706,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { isReadOnly={true} label={k.label || ""} option={k.defaultValues} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} isMultiple={true} /> </div> @@ -1452,12 +1715,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1599,7 +1869,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { <CheckBoxField label={k.label || ""} showLabel={false} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} defaultValues={k.defaultValues} /> </div> @@ -1608,12 +1878,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1754,13 +2031,11 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { <CardThree children={ <> - <div className="ps-4 pe-4 pt-3 mb-4 col-7"> + <div className="ps-4 pe-4 pt-3 mb-4 col-sm-12 col-md-7 col-lg-7"> <FileUploadView showLabel={k.label ? true : false} label={k.label || ""} - value={ - (k.value && k.value.split(",")) || "" - } + value={k.value ? k.value.split(",") : ""} /> </div> <div className=""> @@ -1768,12 +2043,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> @@ -1918,6 +2200,7 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { <BooleanField showLabel={k.label ? true : false} label={k.label || ""} + value={k.value} isReadOnly={true} /> </div> @@ -1926,12 +2209,19 @@ export const FormView = ({ applicationData, formData }: FormViewProps) => { label="Is the given information found correct?" modalTriggerLabel={"Edit"} disableEdit={false} + showAttachment={true} + attachments={k.attachments} clickHandler={(e) => { setModalTextArea(k.comments); setModalInspectionValue( k.inspectionValue ); }} + showAttachmentRemover={true} + attachmentRemoveHandler={(e) => + removeAttachment(e, k.label) + } + id={`attachment${k.label}`} inspectionValue={k.inspectionValue} children={ <div className="d-flex flex-row"> diff --git a/src/layouts/Inspector/InspectionSummaryLayout.tsx b/src/layouts/Inspector/InspectionSummaryLayout.tsx index a2a6e03806d92df55c0777c5d36498f8cb6bd278..a304222c23d6eaa83074113adad2e313a16e9072 100644 --- a/src/layouts/Inspector/InspectionSummaryLayout.tsx +++ b/src/layouts/Inspector/InspectionSummaryLayout.tsx @@ -183,7 +183,9 @@ export const InspectionSummaryLayout = ({ > {k.firstName[0] + k.lastName[0]} </div> - <p className="ps-2 pt-2">{k.firstName}</p> + <p className="ps-2 pt-2"> + {k.firstName + " " + k.lastName} + </p> </div> </div> </div> @@ -217,7 +219,9 @@ export const InspectionSummaryLayout = ({ > {k.firstName[0] + k.lastName[0]} </div> - <p className="ps-2 pt-2">{k.firstName}</p> + <p className="ps-2 pt-2"> + {k.firstName + " " + k.lastName} + </p> </div> </div> </div> diff --git a/src/layouts/Regulator/ReviewApplicationLayout.tsx b/src/layouts/Regulator/ReviewApplicationLayout.tsx index ccb98fad8e9e7fa85446344f050e5b1a6e74b02a..19132c869c4794c7a9ccfa87238979cc9bd0d7c5 100644 --- a/src/layouts/Regulator/ReviewApplicationLayout.tsx +++ b/src/layouts/Regulator/ReviewApplicationLayout.tsx @@ -130,6 +130,7 @@ export const ReviewApplicationLayout = ({ isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); } return null; @@ -171,11 +172,12 @@ export const ReviewApplicationLayout = ({ sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); } else { return tempFormArray.push({ @@ -184,7 +186,7 @@ export const ReviewApplicationLayout = ({ sideMenu: m.sideMenu, label: k.label, value: i.fields[k.label], - defaultValues: k.values, + defaultValues: k.defaultValues, fieldType: k.fieldType, isCorrect: tempArrayTwo[n].fields[k.label]["value"] === "correct" @@ -193,6 +195,7 @@ export const ReviewApplicationLayout = ({ inspectionValue: tempArrayTwo[n].fields[k.label]["inspectionValue"], comments: tempArrayTwo[n].fields[k.label]["comments"], + attachments: tempArrayTwo[n].fields[k.label]["attachments"], }); } }); @@ -210,11 +213,12 @@ export const ReviewApplicationLayout = ({ sideMenu: i.sideMenu, label: m.name, value: i.fields[m.name], - defaultValues: m.values, + defaultValues: m.defaultValues, fieldType: m.fieldType, isCorrect: "", inspectionValue: "", comments: "", + attachments: [], }); }); return null; @@ -276,40 +280,6 @@ export const ReviewApplicationLayout = ({ } }, [selectedMenuLabel, processedData]); - useEffect(() => { - if (reviewerNote[0] !== "") { - let payload = {}; - - if (reviewerNote[0] === "Empty!") { - payload = { - applicationId: applicationData.applicationId, - }; - } else { - payload = { - applicationId: applicationData.applicationId, - notes: reviewerNote[0], - }; - } - - ReviewService.returnApplication(payload).then( - (response) => { - if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { - Notify.success("Application returned to institute"); - } else { - Notify.error(response.statusInfo.errorMessage); - } - }, - (error) => { - error.statusInfo - ? Notify.error(error.statusInfo.errorMessage) - : Notify.error(error.message); - } - ); - history.push(APP.ROUTES.DASHBOARD); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [reviewerNote]); - const getApplicationStatusLog = (id: any) => { ReviewService.getStatusLog(id).then( (response) => { @@ -368,6 +338,40 @@ export const ReviewApplicationLayout = ({ } }; + const returnApplication = (e: any) => { + e.preventDefault(); + + let textAreaElement = document.getElementById("returnModal"); + let comments = textAreaElement?.querySelector("textarea")?.value; + + if (comments !== "" && comments && comments?.length > 5) { + let payload = {}; + + payload = { + applicationId: applicationData.applicationId, + notes: comments, + }; + + ReviewService.returnApplication(payload).then( + (response) => { + if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { + Notify.success("Application returned to institute"); + } else { + Notify.error(response.statusInfo.errorMessage); + } + }, + (error) => { + error.statusInfo + ? Notify.error(error.statusInfo.errorMessage) + : Notify.error(error.message); + } + ); + history.push(APP.ROUTES.DASHBOARD); + } else { + Notify.error("Kindly enter proper review comments!"); + } + }; + return ( <div className=""> {applicationData && ( @@ -480,18 +484,21 @@ export const ReviewApplicationLayout = ({ </div> <ModalTwo id="returnModal" - enableHandler={false} + enableHandler={true} enableSkip={false} ariaLabel="returnModalLabel" showTextAreaLabel={false} heading="Add note" textAreaPlaceholder="Write here" + submitHandler={(e: any) => { + returnApplication(e); + }} /> <ModalTwo id="rejectModal" enableHandler={true} enableSkip={false} - ariaLabel="returnModalLabel" + ariaLabel="rejectModalLabel" showTextAreaLabel={false} heading="Add note" textAreaPlaceholder="Write here" @@ -503,7 +510,7 @@ export const ReviewApplicationLayout = ({ id="approveModal" enableHandler={true} enableSkip={false} - ariaLabel="returnModalLabel" + ariaLabel="approveModalLabel" showTextAreaLabel={false} heading="Add note" textAreaPlaceholder="Write here" @@ -581,7 +588,7 @@ export const ReviewApplicationLayout = ({ : "" } comments={ - applicationData.comments ? applicationData.comments : "" + applicationData.notes ? applicationData.notes : "" } approvedNote={ applicationData.status !== LANG.FORM_STATUS.RETURNED && @@ -639,7 +646,9 @@ export const ReviewApplicationLayout = ({ k.lastName[0]} </div> <p className="ps-2"> - {k.firstName} + {k.firstName + + " " + + k.lastName} </p> </div> </div> @@ -678,7 +687,9 @@ export const ReviewApplicationLayout = ({ k.lastName[0]} </div> <p className="ps-2"> - {k.firstName} + {k.firstName + + " " + + k.lastName} </p> </div> @@ -781,6 +792,101 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} + children={ + <div className="d-flex flex-row"> + {k.isCorrect === "" ? ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={false} + /> + </div> + </> + ) : k.isCorrect === true ? ( + <> + <div className="me-3"> + <Radio + isSelected={true} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={false} + label="Incorrect" + isModal={false} + /> + </div> + </> + ) : ( + <> + <div className="me-3"> + <Radio + isSelected={false} + label="Correct" + /> + </div> + <div className="me-3"> + <Radio + isSelected={true} + label="Incorrect" + isModal={false} + /> + </div> + </> + )} + </div> + } + showComments={ + k.comments !== "" ? true : false + } + comments={k.comments} + /> + </div> + )} + </> + } + /> + </div> + ); + case "email": + return ( + <div className="mt-3" key={l}> + <CardThree + children={ + <> + <div className="ps-4 pe-4 pt-3 col-4"> + <TextField + showLabel={k.label ? true : false} + label={k.label || ""} + type="email" + isReadOnly={true} + value={k.value || ""} + /> + </div> + {(applicationData.status === + LANG.FORM_STATUS.INSPECTION_COMPLETED || + applicationData.status === + LANG.FORM_STATUS.APPROVED || + applicationData.status === + LANG.FORM_STATUS.REJECTED) && ( + <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"> {k.isCorrect === "" ? ( @@ -872,6 +978,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -951,7 +1059,7 @@ export const ReviewApplicationLayout = ({ isReadOnly={true} label={k.label || ""} option={k.defaultValues} - placeholder={k.value || ""} + defaultValue={k.value || ""} /> </div> {(applicationData.status === @@ -965,6 +1073,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1074,6 +1184,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1164,6 +1276,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1255,6 +1369,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1334,7 +1450,7 @@ export const ReviewApplicationLayout = ({ isReadOnly={true} label={k.label || ""} option={k.defaultValues} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} isMultiple={true} /> </div> @@ -1349,6 +1465,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1425,7 +1543,7 @@ export const ReviewApplicationLayout = ({ <CheckBoxField label={k.label || ""} showLabel={false} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} defaultValues={k.defaultValues} /> </div> @@ -1440,6 +1558,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1515,7 +1635,7 @@ export const ReviewApplicationLayout = ({ <FileUploadView showLabel={k.label ? true : false} label={k.label || ""} - value={k.value.split(",") || ""} + value={k.value ? k.value.split(",") : ""} /> </div> {(applicationData.status === @@ -1529,6 +1649,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( @@ -1604,6 +1726,7 @@ export const ReviewApplicationLayout = ({ <BooleanField showLabel={k.label ? true : false} label={k.label || ""} + value={k.value} isReadOnly={true} /> </div> @@ -1618,6 +1741,8 @@ export const ReviewApplicationLayout = ({ label="Is the given information found correct?" inspectionValue={k.inspectionValue} disableEdit={true} + showAttachment={false} + attachments={k.attachments} children={ <div className="d-flex flex-row"> {k.isCorrect === "" ? ( diff --git a/src/layouts/index.ts b/src/layouts/index.ts index 113d4eda6eba35037f11172716b5d895fe22d6ea..bf2607aa8173cc3de95b2fb521cb90fe4355d84e 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -6,3 +6,4 @@ export { ReviewApplicationLayout } from "./Regulator/ReviewApplicationLayout"; export { InspectionSummaryLayout } from "./Inspector/InspectionSummaryLayout"; export { InspectionCompleteLayout } from "./Inspector/InspectionCompleteLayout"; export { ConsentFormView } from "./Inspector/ConsentFormView"; +export { DashboardLayout } from "./Dashboard/DashboardLayout"; diff --git a/src/layouts/reviewer/Users.tsx b/src/layouts/reviewer/Users.tsx index d1915a48f9992caed837396afe1bfd5fd8514d15..2dbeb891c512660716f7455622780fb9aa8d7ae9 100644 --- a/src/layouts/reviewer/Users.tsx +++ b/src/layouts/reviewer/Users.tsx @@ -124,7 +124,7 @@ export const Users = ({ data }: userProps) => { <div className="col-sm-12 col-md-8 text-right"> <BtnTwo btnType="button" - label="Add new" + label="Add new user" isLink={true} link={`/create-user`} floatBottom={false} diff --git a/src/pages/Dashboard/Landing.tsx b/src/pages/Dashboard/Landing.tsx new file mode 100644 index 0000000000000000000000000000000000000000..66fb27f591e654a3922020c7a4e1085e2e99e821 --- /dev/null +++ b/src/pages/Dashboard/Landing.tsx @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { Fragment, useEffect, useState } from "react"; +import { HeadingOne } from "../../components/headings"; +import Header from "../../components/common/Header"; +import { useHistory } from "react-router-dom"; +import { DashboardLayout } from "../../layouts"; +import { ChartService } from "../../services"; +import { APP } from "../../constants"; +import Notify from "../../helpers/notify"; + +/** + * Landing component renders + * dashboard layout and its UI components + */ + +interface LandingProps { + data?: any; +} + +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(); + }, []); + + const getDashboardConfigurations = () => { + ChartService.getDashboardConfig().then((response) => { + if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { + setDashboardConfigData(response.responseData) + } else { + Notify.error(response.statusInfo.errorMessage); + } + }); + }; + + return ( + <Fragment> + <Header history={history} /> + <div className="container-fluid"> + <div className="container dashboard-inner-container mt-4"> + {/* Section one */} + <section className="pt-3"> + <HeadingOne heading="Insights so far" /> + </section> + + {/* Dashboards */} + <section className=""> + <DashboardLayout dashboardConfig={dashboardConfigData}/> + </section> + </div> + </div> + </Fragment> + ); +}; diff --git a/src/pages/Inspector/InspectorApplications.tsx b/src/pages/Inspector/InspectorApplications.tsx index b2a0940c5766e7c7f76853234eec26834d768a55..125a4f417053dbb10829cf95a82dd70700413afb 100644 --- a/src/pages/Inspector/InspectorApplications.tsx +++ b/src/pages/Inspector/InspectorApplications.tsx @@ -67,7 +67,7 @@ export const InspectorApplications = ({ data }: InspectorApplicationsProps) => { setSelectedTab("Scheduled today"); } - let user: any = userDetails.length && JSON.parse(userDetails); + let user: any = userDetails && JSON.parse(userDetails); setUserDetails(user); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -100,67 +100,69 @@ export const InspectorApplications = ({ data }: InspectorApplicationsProps) => { (response) => { if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { response.responseData.map((i: any, j: number) => { - if ( - i.inspection.status === LANG.FORM_STATUS.SENT_FOR_INSPECTION && - i.inspection.leadInspector.includes( - userDetails && userDetails.id - ) - ) { + if (i.inspection) { if ( - moment(todayDate, "DD-MM-YYYY").isBefore( - moment(i.inspection.scheduledDate, "DD-MM-YYYY") + i.inspection.status === + LANG.FORM_STATUS.SENT_FOR_INSPECTION && + i.inspection.leadInspector.includes( + userDetails && userDetails.id ) ) { - setUpcoming((upcoming) => [...upcoming, i]); - } else if ( - moment(todayDate, "DD-MM-YYYY").isSame( - moment(i.inspection.scheduledDate, "DD-MM-YYYY") + if ( + moment(todayDate, "DD-MM-YYYY").isBefore( + moment(i.inspection.scheduledDate, "DD-MM-YYYY") + ) + ) { + setUpcoming((upcoming) => [...upcoming, i]); + } else if ( + moment(todayDate, "DD-MM-YYYY").isSame( + moment(i.inspection.scheduledDate, "DD-MM-YYYY") + ) + ) { + setScheduledToday((today) => [...today, i]); + } else { + setPast((past) => [...past, i]); + } + } + if ( + (i.inspection.status === + LANG.FORM_STATUS.LEAD_INSPECTION_COMPLETED || + i.inspection.status === + LANG.FORM_STATUS.INSPECTION_COMPLETED) && + i.inspection.leadInspector.includes( + userDetails && userDetails.id ) ) { - setScheduledToday((today) => [...today, i]); - } else { setPast((past) => [...past, i]); } - } - - if ( - (i.inspection.status === - LANG.FORM_STATUS.LEAD_INSPECTION_COMPLETED || + if ( i.inspection.status === - LANG.FORM_STATUS.INSPECTION_COMPLETED) && - i.inspection.leadInspector.includes( - userDetails && userDetails.id - ) - ) { - setPast((past) => [...past, i]); - } - - if ( - i.inspection.status === LANG.FORM_STATUS.INSPECTION_COMPLETED && - i.inspection.assistingInspector.includes( - userDetails && userDetails.id - ) - ) { - setPast((past) => [...past, i]); - } - - if ( - i.inspection.status === - LANG.FORM_STATUS.LEAD_INSPECTION_COMPLETED && - i.inspection.assistingInspector.includes( - userDetails && userDetails.id - ) - ) { - i.inspection.assignedTo.map((m: any, n: number) => { - if (m.id === userDetails.id) { - if (m.status === LANG.FORM_STATUS.INSPECTION_COMPLETED) { + LANG.FORM_STATUS.INSPECTION_COMPLETED && + i.inspection.assistingInspector.includes( + userDetails && userDetails.id + ) + ) { + setPast((past) => [...past, i]); + } + if ( + i.inspection.status === + LANG.FORM_STATUS.LEAD_INSPECTION_COMPLETED && + i.inspection.assistingInspector.includes( + userDetails && userDetails.id + ) + ) { + i.inspection.assignedTo.map((m: any, n: number) => { + if (m.id === userDetails.id) { + // if (m.status === LANG.FORM_STATUS.INSPECTION_COMPLETED) { setPast((past) => [...past, i]); + // } + return null; } return null; - } - return null; - }); + }); + } } + return null; }); } else { diff --git a/src/pages/Inspector/InspectorHome.tsx b/src/pages/Inspector/InspectorHome.tsx index 63a40bd477377b0aa504b4acad7d719cea07c3a1..3bf40495481926ebb0a96a3b36d3b0bdc86dce68 100644 --- a/src/pages/Inspector/InspectorHome.tsx +++ b/src/pages/Inspector/InspectorHome.tsx @@ -39,8 +39,10 @@ export const InspectorHome = ({ data }: InspectorProps) => { useEffect(() => { if (userDetails.id) { - getAllApplications(); - getDashboardData(); + setTimeout(() => { + getAllApplications(); + getDashboardData(); + }, 850); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [userDetails]); @@ -80,6 +82,7 @@ export const InspectorHome = ({ data }: InspectorProps) => { if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { response.responseData.map((i: any, j: number) => { if ( + i.inspection && i.inspection.leadInspector.includes( userDetails && userDetails.id ) && @@ -119,6 +122,7 @@ export const InspectorHome = ({ data }: InspectorProps) => { if (response.statusInfo.statusCode === APP.CODE.SUCCESS) { response.responseData.map((i: any, j: number) => { if ( + i.inspection && i.inspection.status === LANG.FORM_STATUS.LEAD_INSPECTION_COMPLETED && !i.inspection.leadInspector.includes( diff --git a/src/pages/Regulator/RegulatorAllApplications.tsx b/src/pages/Regulator/RegulatorAllApplications.tsx index 1af0561af6e3bd7b8d40948bb13de018a73bdda5..04cfe042509e05090a3874748c8959ead9045f19 100644 --- a/src/pages/Regulator/RegulatorAllApplications.tsx +++ b/src/pages/Regulator/RegulatorAllApplications.tsx @@ -40,12 +40,12 @@ export const RegulatorAllApplications = ({ ariaLabelled: "new-tab", children: <AllApplicationsTab />, }, - { - id: "underReview", - label: "Under review", - ariaLabelled: "under-review-tab", - children: <AllApplicationsTab />, - }, + // { + // id: "underReview", + // label: "Under review", + // ariaLabelled: "under-review-tab", + // children: <AllApplicationsTab />, + // }, { id: "returned", label: "Returned", @@ -135,9 +135,9 @@ export const RegulatorAllApplications = ({ tempStatus = e.target.innerHTML; break; - case "Under review": - tempStatus = "underreview"; - break; + // case "Under review": + // tempStatus = "underreview"; + // break; case "Returned": tempStatus = "returned"; diff --git a/src/pages/Regulator/ReviewApplication.tsx b/src/pages/Regulator/ReviewApplication.tsx index e83570b62f131f0fe0f450c055a2c82e651fd355..bba18fb09a0544f469e7e33bed371f4dfb0d9c65 100644 --- a/src/pages/Regulator/ReviewApplication.tsx +++ b/src/pages/Regulator/ReviewApplication.tsx @@ -25,10 +25,11 @@ export const ReviewApplication = ({ data }: ReviewApplicationProps) => { let history = useHistory(); useEffect(() => { + if (history.location && history.location.pathname) { let tempFormId = history.location.pathname.split("/")[2]; let tempAppId = history.location.pathname.split("/")[3]; - + getApplicationDetails(tempFormId, tempAppId); } // eslint-disable-next-line react-hooks/exhaustive-deps diff --git a/src/pages/Reviewer/ReviewerHome.tsx b/src/pages/Reviewer/ReviewerHome.tsx index accce8ffadfdbd22b1df643535b86d0f138f8022..b931554e69ba68544fc621dc281e7dd3316ed573 100644 --- a/src/pages/Reviewer/ReviewerHome.tsx +++ b/src/pages/Reviewer/ReviewerHome.tsx @@ -5,6 +5,7 @@ import Notify from "../../helpers/notify"; import { FormService } from "../../services/form.service"; import { APP, LANG } from "../../constants"; import { BtnOne } from "../../components/buttons"; +import { useHistory } from "react-router-dom"; /** * Reviewer component renders @@ -52,17 +53,41 @@ export const ReviewerHome = ({ data }: ReviewerProps) => { const [applicationsMetrics, setApplicationsMetrics] = useState< IApplicationCount[] >([]); + const [showPendingApplications, setShowPendingApplications] = useState(false); + + let history = useHistory(); + useEffect(() => { + setTimeout(() => { + getPendingApplications(); + }, 850); + }, [history]); + + const getPendingApplications = () => { const myApplicationsReq = { searchObjects: [], }; + FormService.getAllApplications(myApplicationsReq).then( (response2) => { if (response2.statusInfo.statusCode === APP.CODE.SUCCESS) { + let data = response2.responseData; + + let tempArray: any = []; + + data.map((m: any, n: number) => { + if ( + m.status === LANG.FORM_STATUS.NEW || + m.status === LANG.FORM_STATUS.INSPECTION_COMPLETED + ) { + tempArray.push(m); + setShowPendingApplications(true); + } + return null; + }); + setPendingApplications( - response2.responseData.length > 8 - ? response2.responseData.splice(0, 8) - : response2.responseData + tempArray.length > 8 ? tempArray.splice(0, 8) : tempArray ); } else { Notify.error(response2.statusInfo.errorMessage); @@ -88,7 +113,7 @@ export const ReviewerHome = ({ data }: ReviewerProps) => { : Notify.error(error.message); } ); - }, []); + }; // Function to format the status label const formatLabel = (labelStatus: string) => { @@ -132,52 +157,59 @@ export const ReviewerHome = ({ data }: ReviewerProps) => { </section> {/* Section two */} - <section className="mt-5"> - <div className="row"> - <div className="col-md-10 col-sm-12 col-12 "> - <HeadingOne heading="Pending applications" /> - <HeadingTwo heading="These are latest applications that is pending for your review/approval" /> + {showPendingApplications && ( + <section className="mt-5"> + <div className="row"> + <div className="col-md-10 col-sm-12 col-12 "> + <HeadingOne heading="Pending applications" /> + <HeadingTwo heading="These are latest applications that is pending for your review/approval" /> + </div> + <div className="col-md-2 col-sm-12 col-12 text-right"> + <BtnOne + btnType="button" + label="SEE ALL" + isLink={true} + link={`reviewer/all-applications`} + floatBottom={false} + isModal={false} + /> + </div> </div> - <div className="col-md-2 col-sm-12 col-12 text-right"> - <BtnOne - btnType="button" - label="SEE ALL" - isLink={true} - link={`reviewer/all-applications`} - floatBottom={false} - isModal={false} - /> + <div className="row mt-3"> + {pendingApplications.map((i, j) => { + if ( + i.status === LANG.FORM_STATUS.NEW || + i.status === LANG.FORM_STATUS.INSPECTION_COMPLETED + ) { + return ( + <div + className="col-sm-12 col-md-4 col-lg-3 col-xl-3 col-xxl-3 mb-3" + key={i.applicationId} + > + <CardTwo + title={i.title} + name={i.createdBy} + time={`Created on: ${i.createdDate}`} + showStatus={true} + status={i.status} + statusLabel={i.status} + showBtn={true} + type="button" + btnText="View application" + isLink={true} + link={ + "/regulator/" + i.formId + "/" + i.applicationId + } + /> + </div> + ); + } else { + return null; + } + })} </div> - </div> - <div className="row mt-3"> - {pendingApplications.map((i, j) => { - if (i.status === LANG.FORM_STATUS.NEW) { - return ( - <div - className="col-sm-12 col-md-4 col-lg-3 col-xl-3 col-xxl-3 mb-3" - key={i.applicationId} - > - <CardTwo - title={i.title} - name={i.createdBy} - time={`Created on: ${i.createdDate}`} - showStatus={true} - status={i.status} - statusLabel={i.status} - showBtn={true} - type="button" - btnText="View application" - isLink={true} - link={"/regulator/" + i.formId + "/" + i.applicationId} - /> - </div> - ); - } else { - return null; - } - })} - </div> - </section> + </section> + )} </div> </div> </Fragment> diff --git a/src/pages/index.ts b/src/pages/index.ts index 038393ffa67aa325558c449fbe42b8850af26258..fd7753a899f221f49ac3c275d7faccd2df9b86ce 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -6,3 +6,4 @@ export { ReviewApplication } from "./Regulator/ReviewApplication"; export { InspectionSummary } from "./Inspector/InspectionSummary"; export { InspectionComplete } from "./Inspector/InspectionComplete"; export { ViewConsentApplications } from "./Inspector/ViewConsentApplication"; +export { Landing } from "./Dashboard/Landing"; diff --git a/src/services/chart.service.js b/src/services/chart.service.js new file mode 100644 index 0000000000000000000000000000000000000000..7200b96b33a657534abab7db7657cbc739d7b8f8 --- /dev/null +++ b/src/services/chart.service.js @@ -0,0 +1,75 @@ +import { APIS, APP, LANG } from "../constants"; +import { authHeader } from "../helpers/authHeader"; +import Notify from "./../helpers/notify"; +import { UserService } from "./user.service"; + +/** + * Chart service + * Provides API functions, returns + * data required for the charts + */ + +export const ChartService = { + getDashboardConfig, + getDashboardProfile, + getChartData, +}; + +async function getDashboardConfig() { + const requestOptions = { + method: APP.REQUEST.GET, + headers: authHeader(), + }; + return await fetch( + APIS.BASE_URL + APIS.DASHBOARD.GET_DASHBOARD_CONFIG, + requestOptions + ).then(handleResponse); +} + +async function getDashboardProfile() { + const requestOptions = { + method: APP.REQUEST.GET, + headers: authHeader(), + }; + return await fetch( + APIS.BASE_URL + APIS.DASHBOARD.GET_DASHBOARD_PROFILE, + requestOptions + ).then(handleResponse); +} + +async function getChartData(payload) { + const requestOptions = { + method: APP.REQUEST.POST, + body: JSON.stringify(payload), + headers: authHeader(), + }; + return await fetch( + APIS.BASE_URL + APIS.DASHBOARD.GET_CHART_DATA, + requestOptions + ).then(handleResponse); +} + +function handleResponse(response) { + return response.text().then((text) => { + const data = text && JSON.parse(text); + if (!response.ok) { + const error = + LANG.APIERROR || + (data && data.statusInfo && data.statusInfo.errorMessage) || + response.statusText; + return Promise.reject(new Error(error)); + } + if (data && data.statusInfo && data.statusInfo.statusCode) { + if (data.statusInfo.statusCode === 306) { + const error = + (data && data.statusInfo && data.statusInfo.errorMessage) || + response.statusText; + UserService.logout(); + Notify.error(error.message); + window.location.reload(); + return Promise.reject(new Error(error)); + } + } + return data; + }); +} diff --git a/src/services/form.service.js b/src/services/form.service.js index 1f1f9d59affeadda67db8e4adb3f22ef1c74ea50..b0e1ae7216138942cbbc8ddd065f9e9fca3d333e 100644 --- a/src/services/form.service.js +++ b/src/services/form.service.js @@ -14,154 +14,155 @@ export const FormService = { getMyApplications, findApplication, uploadfile, - getApplicationsStatusCount + getApplicationsStatusCount, }; -function get() { +async function get() { const requestOptions = { method: APP.REQUEST.GET, headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.GET, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.GET, requestOptions).then( handleResponse ); } -function find(formId) { +async function find(formId) { const requestOptions = { method: APP.REQUEST.GET, headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.FIND + formId, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.FIND + formId, requestOptions).then( handleResponse ); } -function add(form) { +async function add(form) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(form), headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.ADD, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.ADD, requestOptions).then( handleResponse ); } -function update(form) { +async function update(form) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(form), headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.UPDATE, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.UPDATE, requestOptions).then( handleResponse ); } -function remove(form) { +async function remove(form) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(form), headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.DELETE, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.DELETE, requestOptions).then( handleResponse ); } -function submit(form) { +async function submit(form) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(form), headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.FORM.SUBMIT, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.FORM.SUBMIT, requestOptions).then( handleResponse ); } -function getAllApplications(req) { +async function getAllApplications(req) { const requestOptions = { method: APP.REQUEST.POST, headers: authHeader(), - body: JSON.stringify(req) + body: JSON.stringify(req), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.FORM.GET_ALL_APPLICATIONS, requestOptions ).then(handleResponse); } -function getMyApplications(req) { +async function getMyApplications(req) { const requestOptions = { method: APP.REQUEST.POST, headers: authHeader(), - body: JSON.stringify(req) + body: JSON.stringify(req), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.FORM.GET_ALL_APPLICATIONS + "?myApplication=true'", requestOptions ).then(handleResponse); } -function getApplicationsStatusCount() { +async function getApplicationsStatusCount() { const requestOptions = { method: APP.REQUEST.GET, headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.FORM.GET__APPLICATIONS_STATUS_COUNT, requestOptions ).then(handleResponse); } -function findApplication(applicationId) { +async function findApplication(applicationId) { const requestOptions = { method: APP.REQUEST.GET, headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.FORM.GET_APPLICATION_DETAILS + applicationId, requestOptions ).then(handleResponse); } -function uploadfile(form) { +async function uploadfile(form) { const requestOptions = { method: APP.REQUEST.POST, body: form, headers: { - ...authHeaderForUpload(), - // 'Accept': 'application/json', - // 'Content-Type': 'multipart/form-data' + ...authHeaderForUpload(), + // 'Accept': 'application/json', + // 'Content-Type': 'multipart/form-data' }, }; - return fetch(APIS.BASE_URL + APIS.FORM.FILE_UPLOAD, - requestOptions, - ).then(handleResponse); + return await fetch(APIS.BASE_URL + APIS.FORM.FILE_UPLOAD, requestOptions).then( + handleResponse + ); } function handleResponse(response) { - return response.text().then((text) => { const data = text && JSON.parse(text); if (!response.ok) { const error = - LANG.APIERROR || (data && data.statusInfo && data.statusInfo.errorMessage) || response.statusText; + LANG.APIERROR || + (data && data.statusInfo && data.statusInfo.errorMessage) || + response.statusText; return Promise.reject(new Error(error)); } - if(data && data.statusInfo && data.statusInfo.statusCode) { - if(data.statusInfo.statusCode === 306) { + if (data && data.statusInfo && data.statusInfo.statusCode) { + if (data.statusInfo.statusCode === 306) { const error = - (data && data.statusInfo && data.statusInfo.errorMessage) || response.statusText; - UserService.logout() - Notify.error(error.message) - window.location.reload() + (data && data.statusInfo && data.statusInfo.errorMessage) || + response.statusText; + UserService.logout(); + Notify.error(error.message); + window.location.reload(); return Promise.reject(new Error(error)); } } return data; }); } - diff --git a/src/services/index.ts b/src/services/index.ts index c9237033708069aa301f56d9c545488a1182ec2b..53c49458eed0f020f8790ed0225bfa1e22d078f4 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,3 +1,4 @@ export { ReviewService } from "./review.service"; export { FormService } from "./form.service"; export { UserService } from "./user.service"; +export { ChartService } from "./chart.service"; diff --git a/src/services/review.service.js b/src/services/review.service.js index 4934cab0cef156618e8cc107339c894e656142d6..1f1ea5f8ad1ae74c4e1360d24d6e0dbd21653cac 100644 --- a/src/services/review.service.js +++ b/src/services/review.service.js @@ -14,31 +14,31 @@ export const ReviewService = { consentApplication, }; -function returnApplication(payload) { +async function returnApplication(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.REGULATOR.RETURN_APPLICATION, requestOptions ).then(handleResponse); } -function assignToInspection(payload) { +async function assignToInspection(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.REGULATOR.ASSIGN_TO_INSPECTION, requestOptions ).then(handleResponse); } -function getAllInspectors() { +async function getAllInspectors() { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify({ @@ -47,65 +47,65 @@ function getAllInspectors() { }), headers: authHeader(), }; - return fetch(APIS.BASE_URL + APIS.USER.GET_ALL_USERS, requestOptions).then( + return await fetch(APIS.BASE_URL + APIS.USER.GET_ALL_USERS, requestOptions).then( handleResponse ); } -function getStatusLog(applicationId) { +async function getStatusLog(applicationId) { const requestOptions = { method: APP.REQUEST.GET, headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.REGULATOR.GET_STATUS_LOG + applicationId, requestOptions ).then(handleResponse); } -function submitInspectionDetails(payload) { +async function submitInspectionDetails(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.INSPECTOR.SUBMIT_INSPECTION_DETAILS, requestOptions ).then(handleResponse); } -function approveApplication(payload) { +async function approveApplication(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.REGULATOR.APPROVE_APPLICATION, requestOptions ).then(handleResponse); } -function rejectApplication(payload) { +async function rejectApplication(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.REGULATOR.REJECT_APPLICATION, requestOptions ).then(handleResponse); } -function consentApplication(payload) { +async function consentApplication(payload) { const requestOptions = { method: APP.REQUEST.POST, body: JSON.stringify(payload), headers: authHeader(), }; - return fetch( + return await fetch( APIS.BASE_URL + APIS.INSPECTOR.CONSENT_APPLICATION, requestOptions ).then(handleResponse); diff --git a/src/states/atoms.tsx b/src/states/atoms.tsx index 2c691e611ee4fe8dd60f2743928a757f23593cdd..623c12224fdf68a8fd7e3c10e3f3a30b32f41cc5 100644 --- a/src/states/atoms.tsx +++ b/src/states/atoms.tsx @@ -38,3 +38,9 @@ export const dataObjectInspectionForm = atom({ key: "dataObjectInspectionForm", default: "", }); + +export const dataObjectFileUpload = atom({ + key: "dataObjectFileUpload", + default: {}, +}); + diff --git a/src/styles/chart.css b/src/styles/chart.css new file mode 100644 index 0000000000000000000000000000000000000000..269f0066cab901b66ed19406db039084a9454ffc --- /dev/null +++ b/src/styles/chart.css @@ -0,0 +1,57 @@ +@import url("./colors.css"); +@import url("../assets/fonts/fonts.css"); + +.chart_title_one { + color: var(--black-87); + font-family: "Montserrat-SemiBold"; + font-size: 1rem; + letter-spacing: 0.12px; + line-height: 1.5; +} + +.chart_card_one { + background-color: var(--white-100); + border: 1px solid var(--black-08); + border-radius: 4px; + padding: 1.75rem; +} + +.widget_card_one { + background-color: var(--white-100); + border-radius: 4px; + border: 1px solid var(--black-08); + min-height: 5.563rem; +} + +.widget_card_one label { + color: var(--black-87); + font-family: "Lato-Regular"; + font-size: 0.875rem; + letter-spacing: 0.25px; + line-height: 1.5; +} + +.widget_card_one h2 { + color: var(--black-87); + font-family: "Montserrat-SemiBold"; + font-size: 1.25rem; + letter-spacing: 0.12px; + line-height: 1.4; +} + +.custom_cursor { + cursor: pointer; +} + +.dropdown_text { + font-family: "Lato-Regular"; + font-size: 0.875rem; +} + +.chart_card_description_one { + color: var(--black-60); + font-family:"Lato-Regular"; + font-size: 0.875rem; + letter-spacing: 0.25; + line-height: 1.429; +} diff --git a/yarn.lock b/yarn.lock index 12a63aa67e1d6a7eb61d1fd968db17787c76c378..f97eb311c7636315f113951dd6e21cc536029b72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1315,6 +1315,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@popperjs/core@^2.11.4": + version "2.11.4" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503" + integrity sha512-q/ytXxO5NKvyT37pmisQAItCFqA7FD/vNb8dgaJy3/630Fsc+Mz9/9f2SziBoIZ30TJooXyTwZmhi1zjXmObYg== + "@rollup/plugin-babel@^5.2.0": version "5.3.0" resolved "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz" @@ -1720,6 +1725,11 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash@^4.14.118": + version "4.14.180" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" + integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== + "@types/mime@^1": version "1.3.2" resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz" @@ -2782,6 +2792,16 @@ char-regex@^2.0.0: resolved "https://registry.npmjs.org/char-regex/-/char-regex-2.0.0.tgz" integrity sha512-oGu2QekBMXgyQNWPDRQ001bjvDnZe4/zBTz37TMbiKz1NbNiyiH5hRkobe7npRN6GfbGbxMYFck/vQ1r9c1VMA== +chart.js@^3.4.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.1.tgz#0516f690c6a8680c6c707e31a4c1807a6f400ada" + integrity sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA== + +chartjs-plugin-datalabels@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.0.0.tgz#caacefb26803d968785071eab012dde8746c5939" + integrity sha512-WBsWihphzM0Y8fmQVm89+iy99mmgejmj5/jcsYqwxSioLRL/zqJ4Scv/eXq5ZqvG3TpojlGzZLeaOaSvDm7fwA== + check-types@^11.1.1: version "11.1.2" resolved "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz" @@ -3499,6 +3519,11 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-to-image@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/dom-to-image/-/dom-to-image-2.6.0.tgz#8a503608088c87b1c22f9034ae032e1898955867" + integrity sha1-ilA2CAiMh7HCL5A0rgMuGJiVWGc= + domelementtype@1: version "1.3.1" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz" @@ -3992,6 +4017,11 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + exit@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" @@ -4105,6 +4135,11 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" +file-saver@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38" + integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA== + filelist@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz" @@ -5694,12 +5729,12 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6987,6 +7022,11 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-chartjs-2@^3.0.3: + version "3.3.0" + resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-3.3.0.tgz#3f62681645acccc1ab27a9186c70f79d6d417f73" + integrity sha512-4Mt0SR2aiUbWi/4762odRBYSnbNKSs4HWc0o3IW43py5bMfmfpeZU95w6mbvtuLZH/M3GsPJMU8DvDc+5U9blQ== + react-confirm-alert@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/react-confirm-alert/-/react-confirm-alert-2.7.0.tgz#7cdbf2e052e03554ac6a7d8956c9c227010c8a53" @@ -7046,6 +7086,11 @@ react-is@^17.0.1: resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-lifecycles-compat@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-loadable@^5.5.0: version "5.5.0" resolved "https://registry.npmjs.org/react-loadable/-/react-loadable-5.5.0.tgz" @@ -7060,6 +7105,16 @@ react-localization@^1.0.17: dependencies: localized-strings "^0.2.0" +react-modal@^3.14.4: + version "3.14.4" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.4.tgz#2ca7e8e9a180955e5c9508c228b73167c1e6f6a3" + integrity sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + react-refresh@^0.11.0: version "0.11.0" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz" @@ -8412,6 +8467,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz"