diff --git a/src/Components/CollapseUsage/PanelHeader.jsx b/src/Components/CollapseUsage/PanelHeader.jsx index 47ba6af4..263072a4 100644 --- a/src/Components/CollapseUsage/PanelHeader.jsx +++ b/src/Components/CollapseUsage/PanelHeader.jsx @@ -1,7 +1,9 @@ -import React from "react"; +import React, { useState } from "react"; import { Switch } from "antd"; import { withNamespaces } from "react-i18next"; import { Popover } from "antd"; +import { ModalComponentWithNamespace } from "../modals/modal"; +import { DataAgreementDetailsWithNamespace } from "../modals/detailsContainer/dataAgreementDetails"; const content = (description) => (
@@ -21,32 +23,85 @@ export const PanelHeader = ({ disabled, checked, elemRef, -}) => ( - -
-
-
+ showDescription, + lawfulBasisOfProcessing, + policyURL, + jurisdiction, + thirdPartyDisclosure, + industryScope, + geographicRestriction, + retentionPeriod, + storageLocation, +}) => { + const [openViewDataAgreementModal, setOpenViewDataAgreementModal] = useState( + false + ); + + return ( + <> + +
-

{text}

+
+
+

{text}

+
+ +
+ {showDescription && ( +

+ {description} + + {t("dataAgreements.read")}{" "} + { + e.stopPropagation(); + setOpenViewDataAgreementModal(true); + }} + style={{ textDecoration: "underline", color: "#1890FF" }} + > + {t("dataAgreements.daDetails")} + + +

+ )}
{t("userRequests.panelAllow")} {` ${consented} of ${total}`}
-
-
-
- -); + + + + + + + ); +}; -export const PanelHeaderWithNamespace = withNamespaces()(PanelHeader); +export const PanelHeaderWithNamespace = withNamespaces()(PanelHeader); \ No newline at end of file diff --git a/src/Components/CollapseUsage/collapse.css b/src/Components/CollapseUsage/collapse.css index 1424f666..960c9ef9 100644 --- a/src/Components/CollapseUsage/collapse.css +++ b/src/Components/CollapseUsage/collapse.css @@ -54,14 +54,14 @@ .panel-header { display: flex; - align-items: center; + align-items: top; justify-content: space-between; } .panel-header .title { margin: 0; line-height: 22px; - font-size: "14px"; + font-size: 14px; } .panel-header-count { @@ -192,6 +192,23 @@ padding: 5px 5px; } -/*.ant-collapse > .panel-usage > .ant-collapse-header .ant-collapse-arrow {*/ -/* margin-top: 22px;*/ -/*}*/ +.description-container { + width: 100%; + background-color: yellow; + padding: 8px; + display: flex; + justify-content: space-between; + align-items: flex-start; + font-size: 12px; +} + +.description-text { + flex-grow: 1; + margin-right: 8px; + word-wrap: break-word; + overflow-wrap: break-word; +} + +.read-text { + white-space: nowrap; +} diff --git a/src/Components/CollapseUsage/index.js b/src/Components/CollapseUsage/index.js index f8c11fa6..42270c4c 100644 --- a/src/Components/CollapseUsage/index.js +++ b/src/Components/CollapseUsage/index.js @@ -1,112 +1,179 @@ -import React, { Component, createRef } from 'react'; -import { observer } from 'mobx-react'; -import './collapse.css'; - -import { Collapse, Icon, Spin } from 'antd'; -import { PanelHeaderWithNamespace } from './PanelHeader'; -import { SubCollapse } from './SubCollapse'; -import { store } from 'Provider/store'; +import React, { Component, createRef } from "react"; +import { observer } from "mobx-react"; +import "./collapse.css"; + +import { Collapse, Icon, Spin } from "antd"; +import { PanelHeaderWithNamespace } from "./PanelHeader"; +import { SubCollapse } from "./SubCollapse"; +import { store } from "Provider/store"; import { withNamespaces } from "react-i18next"; const Panel = Collapse.Panel; - const antIcon = ; @observer class CollapseUsage extends Component { + constructor(props) { + super(props); + this.elemRefs = []; + + this.state = { + openKey: [], + scrolled: false, + activeKeys: [], + }; - constructor(props) { - super(props); - this.elemRefs = []; - - this.state = { - openKey: [], - scrolled: false - }; - - this.delta = this.delta.bind(this); - } - - delta(index) { - this.setState({ openKey: index }); - - } - - componentDidMount() { - store.dataAgreements.isFetching = true; - store.fetchPurposes(); + this.delta = this.delta.bind(this); + } + + delta(index) { + this.setState({ openKey: index }); + } + + componentDidMount() { + store.dataAgreements.isFetching = true; + store.fetchPurposes(); + } + + handleSwitch = (dataAgreement, value, e) => { + e.stopPropagation(); + if (dataAgreement["isUpdate"]) { + store.updateConsentRecord( + dataAgreement["consentRecords"]["id"], + dataAgreement["id"], + dataAgreement["consentRecords"]["individualId"], + value + ); + } else { + store.createConsentRecord(dataAgreement["id"]); } + }; + onCollapseChange = (activeKeys) => { + this.setState({ activeKeys }); + }; + render() { + const { isFetching, data, loadingUiStore } = store.dataAgreements; - handleSwitch = (dataAgreement, value, e) => { - e.stopPropagation(); - if (dataAgreement["isUpdate"]) { - store.updateConsentRecord(dataAgreement["consentRecords"]["id"], dataAgreement["id"], dataAgreement["consentRecords"]["individualId"], value); - } else { - store.createConsentRecord(dataAgreement["id"]); - } - }; - + const { t } = this.props; + let openKey = ""; + const { activeKeys } = this.state; - render() { - const { isFetching, data, loadingUiStore } = store.dataAgreements; - const { t } = this.props; - let openKey = ""; - if (isFetching) { - return ( -
-
- ) - } - - this.elemRefs = data.reduce((acc, value) => { - acc[value['id']] = createRef(); - return acc; - }, {}); - - // Todo : Need to fix the toggle, purpose highlight. - - return ( - - !data ? null : } - > - {!data ? null : data.map((dataAgreement, i) => { - const consented = dataAgreement["consentRecords"] === null ? 0 : (dataAgreement["consentRecords"]["optIn"] ? dataAgreement['dataAttributes'].length : 0); - const checked = dataAgreement["consentRecords"] === null ? false : dataAgreement["consentRecords"]["optIn"]; - return ( - - this.handleSwitch(dataAgreement, value, e)} - text={`${dataAgreement['purpose']}`} - description={`${dataAgreement['purposeDescription']}`} - /> - } key={i} className={`panel-usage panel-usage-${dataAgreement['id']}`}> - - {!dataAgreement['dataAttributes'] ? null : dataAgreement['dataAttributes'].map((dataAttribute, i) => - - )} - - ) - })} - - ); + if (isFetching) { + return ( +
+ +
+ ); } + + this.elemRefs = data.reduce((acc, value) => { + acc[value["id"]] = createRef(); + return acc; + }, {}); + + // Todo : Need to fix the toggle, purpose highlight. + + return !data ? null : ( + ( + + )} + > + {!data + ? null + : data.map((dataAgreement, i) => { + console.log("dataAgreement", dataAgreement["consentRecords"]); + const consented = + dataAgreement["consentRecords"] === null + ? 0 + : dataAgreement["consentRecords"]["optIn"] + ? dataAgreement["dataAttributes"].length + : 0; + const checked = + dataAgreement["consentRecords"] === null + ? false + : dataAgreement["consentRecords"]["optIn"]; + + const isActive = activeKeys.includes(i.toString()); + return ( + + + this.handleSwitch(dataAgreement, value, e) + } + text={`${dataAgreement["purpose"]}`} + description={`${dataAgreement["purposeDescription"]}`} + showDescription={isActive} + dataAgreement={dataAgreement} + lawfulBasisOfProcessing={`${ + dataAgreement["lawfulBasis"] + }`} + jurisdiction={dataAgreement["policy"]["jurisdiction"]} + thirdPartyDisclosure={ + dataAgreement["policy"]["thirdPartyDataSharing"] + ? "True" + : "False" + } + industryScope={ + dataAgreement["policy"]["industrySector"] + } + geographicRestriction={ + dataAgreement["policy"]["geographicRestriction"] + } + retentionPeriod={`${Math.floor( + dataAgreement["policy"]["dataRetentionPeriodDays"] / + 365 + )} years`} + storageLocation={ + dataAgreement["policy"]["storageLocation"] + } + /> + + } + key={i} + className={`panel-usage panel-usage-${dataAgreement["id"]}`} + > + {!dataAgreement["dataAttributes"] + ? null + : dataAgreement["dataAttributes"].map( + (dataAttribute, i) => ( + + ) + )} + + ); + })} + + ); + } } -export default withNamespaces()(CollapseUsage); +export default withNamespaces()(CollapseUsage); \ No newline at end of file diff --git a/src/Components/modals/detailsContainer/dataAgreementDetails.js b/src/Components/modals/detailsContainer/dataAgreementDetails.js new file mode 100644 index 00000000..19840f43 --- /dev/null +++ b/src/Components/modals/detailsContainer/dataAgreementDetails.js @@ -0,0 +1,105 @@ +import React from "react"; +import { Card } from "antd"; +import { withNamespaces } from "react-i18next"; + +const cardStyle = { + width: "100%", + border: "none", + borderRadius: "10px", + backgroundColor: "#F7F6F6", +}; + +const DataItem = ({ label, value, isLast }) => ( +
+

{label}

+

{value}

+
+); + +export const DataAgreementDetails = ({ + t, + purpose, + purposeDescription, + lawfulBasisOfProcessing, + policyURL, + jurisdiction, + thirdPartyDisclosure, + industryScope, + geographicRestriction, + retentionPeriod, + storageLocation, +}) => { + const data1 = [ + { key: t("dataAgreements.purpose"), value: purpose }, + { key: t("dataAgreements.purposeDescription"), value: purposeDescription }, + { + key: t("dataAgreements.lawfulBasisOfProcessing"), + value: lawfulBasisOfProcessing, + }, + ]; + + const data2 = [ + { key: t("dataAgreements.policyURL"), value: policyURL }, + { key: t("dataAgreements.jurisdiction"), value: jurisdiction }, + { + key: t("dataAgreements.thirdPartyDisclosure"), + value: thirdPartyDisclosure, + }, + { key: t("dataAgreements.industryScope"), value: industryScope }, + { + key: t("dataAgreements.geographicRestriction"), + value: geographicRestriction, + }, + { + key: t("dataAgreements.retentionPeriod"), + value: retentionPeriod, + }, + { + key: t("dataAgreements.storageLocation"), + value: storageLocation, + }, + ]; + + return ( + <> + + {data1.map(({ key, value }, index) => ( + + ))} + + + {data2.map(({ key, value }, index) => ( + + ))} + + + ); +}; + +export const DataAgreementDetailsWithNamespace = withNamespaces()( + DataAgreementDetails +); + +export default DataAgreementDetailsWithNamespace; diff --git a/src/Components/modals/modal.css b/src/Components/modals/modal.css new file mode 100644 index 00000000..78c0dd45 --- /dev/null +++ b/src/Components/modals/modal.css @@ -0,0 +1,51 @@ +:root { + --drawer-width: 480px; +} + +.my-custom-drawer { + width: var(--drawer-width) !important; +} + +.my-custom-drawer .ant-drawer-header { + border-bottom: none; +} + +.my-custom-drawer .ant-drawer-wrapper-body { + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.footer-container { + position: absolute; + right: 0; + bottom: 0; + width: 100%; + padding: 10px 16px; + background: #fff; + text-align: right; +} + +.button { + height: 30px; + width: 150px; + border-radius: 5; + border: 1px solid #dfdfdf; + color: black; +} + +.button:hover { + background-color: black; + color: white; + border: 1px solid #dfdfdf; +} + +@media screen and (max-width: 600px) { + :root { + --drawer-width: 380px; + } +} + +@media screen and (max-width: 400px) { + :root { + --drawer-width: 100%; + } +} diff --git a/src/Components/modals/modal.js b/src/Components/modals/modal.js new file mode 100644 index 00000000..0b0cb1ee --- /dev/null +++ b/src/Components/modals/modal.js @@ -0,0 +1,38 @@ +import React from "react"; +import "antd/dist/antd.css"; +import { Drawer, Button } from "antd"; +import "./modal.css"; +import { withNamespaces } from "react-i18next"; + +export const ModalComponent = ({ t, open, setOpen, header, children }) => { + return ( + { + e.stopPropagation(); + setOpen(false); + }} + visible={open} + title={header} + className="my-custom-drawer" + width="var(--drawer-width)" + > + {children} + +
+ +
+
+ ); +}; + +export const ModalComponentWithNamespace = withNamespaces()(ModalComponent); diff --git a/src/localization/locales/en/translation.json b/src/localization/locales/en/translation.json index 62aeec80..331e568c 100644 --- a/src/localization/locales/en/translation.json +++ b/src/localization/locales/en/translation.json @@ -79,9 +79,25 @@ "log": "Log" }, "dashboard": { - "name": "Consent Building Block - Privacy Dashboard", + "name": "Your Data and Privacy Dashboard", "logout": "Logout", "logs": "History", "userRequest": "User requests" + }, + "dataAgreements" : { + "read" : "Read", + "daDetails" : "data agreement details.", + "daataAgreement" : "Data Agreement", + "close": "Close", + "purpose" : "Purpose", + "purposeDescription" : "Purpose Description", + "lawfulBasisOfProcessing" : "Lawful Basis of Processing", + "policyURL" : "Policy URL", + "jurisdiction" : "Jurisdiction", + "thirdPartyDisclosure" : "Third Party Disclosure", + "industryScope" : "Industry Scope", + "geographicRestriction" : "Geographic Restriction", + "retentionPeriod" : "Retention Period", + "storageLocation" : "Storage location" } } diff --git a/src/localization/locales/pt/translation.json b/src/localization/locales/pt/translation.json index 3e9ae1a4..a3ef6669 100644 --- a/src/localization/locales/pt/translation.json +++ b/src/localization/locales/pt/translation.json @@ -83,5 +83,21 @@ }, "userPurposes": { "fetchingMessage": "Objetivos de busca..." + }, + "dataAgreements" : { + "read" : "Read", + "daDetails" : "data agreement details.", + "daataAgreement" : "Data Agreement", + "close": "Close", + "purpose" : "Purpose", + "purposeDescription" : "Purpose Description", + "lawfulBasisOfProcessing" : "Lawful Basis of Processing", + "policyURL" : "Policy URL", + "jurisdiction" : "Jurisdiction", + "thirdPartyDisclosure" : "Third Party Disclosure", + "industryScope" : "Industry Scope", + "geographicRestriction" : "Geographic Restriction", + "retentionPeriod" : "Retention Period", + "storageLocation" : "Storage location" } } diff --git a/src/localization/locales/sv/translation.json b/src/localization/locales/sv/translation.json index 053f6b6d..8f4221dd 100644 --- a/src/localization/locales/sv/translation.json +++ b/src/localization/locales/sv/translation.json @@ -83,5 +83,21 @@ "logout": "Logga ut", "logs": "Historik", "userRequest": "Användarförfrågningar" + }, + "dataAgreements" : { + "read" : "Read", + "daDetails" : "data agreement details.", + "daataAgreement" : "Data Agreement", + "close": "Close", + "purpose" : "Purpose", + "purposeDescription" : "Purpose Description", + "lawfulBasisOfProcessing" : "Lawful Basis of Processing", + "policyURL" : "Policy URL", + "jurisdiction" : "Jurisdiction", + "thirdPartyDisclosure" : "Third Party Disclosure", + "industryScope" : "Industry Scope", + "geographicRestriction" : "Geographic Restriction", + "retentionPeriod" : "Retention Period", + "storageLocation" : "Storage location" } }