From 34806731980749995249b2326562f6bd4feb1304 Mon Sep 17 00:00:00 2001 From: Tai Wilkin-Corraggio Date: Tue, 18 Jan 2022 12:32:50 -0500 Subject: [PATCH] Add extended field handling to facility details Makes the facility details subsections expandable to show additional content (details from other matches, claims, etc.) when not in embed mode. Adds extended field subsections (number of workers, native language name, and so on) when values are present and the map is not in embed mode. Adds nonstandard/contributor field subsections when values are present in embed mode. --- CHANGELOG.md | 2 + .../FacilityDetailSidebarContributors.jsx | 55 ++++++++---- .../FacilityDetailSidebarDetail.jsx | 53 +++-------- .../FacilityDetailSidebarExtended.jsx | 90 +++++++++++++++++-- .../FacilityDetailSidebarHeader.jsx | 2 +- .../components/FacilityDetailSidebarItem.jsx | 66 +++++++++++--- .../FacilityDetailSidebarLocation.jsx | 7 +- src/app/src/util/constants.js | 34 +++++++ 8 files changed, 232 insertions(+), 77 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e60884917..9f626c83e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added +- Add extended field handling to facility details [#1593](https://github.com/open-apparel-registry/open-apparel-registry/pull/1593) + ### Changed - Identify exact matches pre-dedupe [#1568](https://github.com/open-apparel-registry/open-apparel-registry/pull/1568) diff --git a/src/app/src/components/FacilityDetailSidebarContributors.jsx b/src/app/src/components/FacilityDetailSidebarContributors.jsx index 67fb35408..486813738 100644 --- a/src/app/src/components/FacilityDetailSidebarContributors.jsx +++ b/src/app/src/components/FacilityDetailSidebarContributors.jsx @@ -1,9 +1,11 @@ import React from 'react'; import { withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Divider from '@material-ui/core/Divider'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; -import FacilityDetailSidebarDetail from './FacilityDetailSidebarDetail'; +import ShowOnly from './ShowOnly'; +import BadgeVerified from './BadgeVerified'; import { makeProfileRouteLink } from '../util/util'; @@ -18,6 +20,9 @@ const detailsSidebarStyles = theme => textTransform: 'uppercase', fontWeight: theme.typography.fontWeightMedium, }, + primaryText: { + wordWrap: 'break-word', + }, secondaryText: { color: 'rgba(0, 0, 0, 0.54)', display: 'flex', @@ -25,8 +30,14 @@ const detailsSidebarStyles = theme => fontSize: '12px', justify: 'flex-end', }, - divider: { - backgroundColor: 'rgba(0, 0, 0, 0.06)', + icon: { + color: 'rgb(106, 106, 106)', + fontSize: '24px', + fontWeight: 300, + textAlign: 'center', + }, + contributor: { + boxShadow: '0px -1px 0px 0px rgb(240, 240, 240)', }, }); @@ -38,18 +49,30 @@ const FacilityDetailSidebarContributors = ({ classes, contributors, push }) => { return (
Contributors - - {contributors.map(contributor => ( - push(makeProfileRouteLink(contributor.id))} - key={contributor.id} - verified={contributor.is_verified} - /> + {visibleContributors.map(contributor => ( + { + if (!contributor.id) return; + push(makeProfileRouteLink(contributor.id)); + }} + key={`${contributor.id} ${contributor.list_name}`} + className={classes.contributor} + > + + + + + + + + ))}
); diff --git a/src/app/src/components/FacilityDetailSidebarDetail.jsx b/src/app/src/components/FacilityDetailSidebarDetail.jsx index b630e952b..5eb1ed027 100644 --- a/src/app/src/components/FacilityDetailSidebarDetail.jsx +++ b/src/app/src/components/FacilityDetailSidebarDetail.jsx @@ -2,8 +2,6 @@ import React from 'react'; import { withStyles } from '@material-ui/core/styles'; import ListItem from '@material-ui/core/ListItem'; import ListItemText from '@material-ui/core/ListItemText'; -import Divider from '@material-ui/core/Divider'; -import ArrowForwardIcon from '@material-ui/icons/ChevronRight'; import ShowOnly from './ShowOnly'; import BadgeVerified from './BadgeVerified'; @@ -13,56 +11,27 @@ const detailsSidebarStyles = () => primaryText: { wordWrap: 'break-word', }, - secondaryText: { - color: 'rgba(0, 0, 0, 0.54)', - display: 'flex', - alignItems: 'center', - fontSize: '12px', - justify: 'flex-end', - }, - divider: { - backgroundColor: 'rgba(0, 0, 0, 0.06)', + item: { + boxShadow: '0px -1px 0px 0px rgb(240, 240, 240)', }, }); const FacilityDetailSidebarDetail = ({ - hasAdditionalContent, - additionalContentCount, primary, secondary, - onClick, - hideTopDivider, verified, classes, }) => ( - <> - - + + + - { - if (!hasAdditionalContent) return; - onClick(); - }} - > - - - - - -
- - -
-
-
- - + +
); export default withStyles(detailsSidebarStyles)(FacilityDetailSidebarDetail); diff --git a/src/app/src/components/FacilityDetailSidebarExtended.jsx b/src/app/src/components/FacilityDetailSidebarExtended.jsx index dcfe5e647..4275d2057 100644 --- a/src/app/src/components/FacilityDetailSidebarExtended.jsx +++ b/src/app/src/components/FacilityDetailSidebarExtended.jsx @@ -1,9 +1,10 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useMemo } from 'react'; import { Redirect } from 'react-router'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import get from 'lodash/get'; import includes from 'lodash/includes'; +import filter from 'lodash/filter'; import moment from 'moment'; import CircularProgress from '@material-ui/core/CircularProgress'; import List from '@material-ui/core/List'; @@ -22,7 +23,10 @@ import { resetSingleFacility, } from '../actions/facilities'; -import { facilitySidebarActions } from '../util/constants'; +import { + facilitySidebarActions, + EXTENDED_FIELD_TYPES, +} from '../util/constants'; import { makeReportADataIssueEmailLink, @@ -80,6 +84,31 @@ const detailsSidebarStyles = theme => const formatAttribution = (createdAt, contributor) => `${moment(createdAt).format('LL')} by ${contributor}`; +/* eslint-disable camelcase */ +const formatExtendedField = ({ + value, + updated_at, + contributor_name, + verified, + id, + formatValue = v => v, +}) => ({ + primary: formatValue(value), + secondary: formatAttribution(updated_at, contributor_name), + verified, + key: id, +}); + +const formatOtherValues = (data, fieldName, extendedFieldName) => [ + ...get(data, `properties.${fieldName}`, []).map(item => ({ + primary: item, + key: item, + })), + ...get(data, `properties.extended_fields.${extendedFieldName}`, []).map( + formatExtendedField, + ), +]; + const FacilityDetailSidebar = ({ classes, data, @@ -105,6 +134,16 @@ const FacilityDetailSidebar = ({ // Clears the selected facility when unmounted useEffect(() => () => clearFacility(), []); + const otherNames = useMemo( + () => formatOtherValues(data, 'other_names', 'name'), + [data], + ); + + const otherAddresses = useMemo( + () => formatOtherValues(data, 'other_addresses', 'address'), + [data], + ); + if (fetching) { return (
@@ -153,6 +192,30 @@ const FacilityDetailSidebar = ({ const isClaimed = !!data?.properties?.claim_info; const claimFacility = () => push(makeClaimFacilityLink(oarId)); + const renderExtendedField = ({ label, fieldName, formatValue }) => { + const values = get(data, `properties.extended_fields.${fieldName}`, []); + if (!values.length || !values[0]) return null; + + const formatField = item => + formatExtendedField({ ...item, formatValue }); + + const topValue = formatField(values[0]); + + return ( + + ); + }; + + const contributorFields = filter( + get(data, 'properties.contributor_fields', null), + field => field.value !== null, + ); + return (
@@ -171,29 +234,46 @@ const FacilityDetailSidebar = ({ oarId={data.properties.oar_id} onClaimFacility={claimFacility} /> - +
+ {EXTENDED_FIELD_TYPES.map(renderExtendedField)} + + + {contributorFields.map(field => ( + + ))} +
, style: { - background: 'rgb(9, 18, 50)', + background: 'rgb(61, 50, 138)', color: 'rgb(255, 255, 255)', }, }; diff --git a/src/app/src/components/FacilityDetailSidebarItem.jsx b/src/app/src/components/FacilityDetailSidebarItem.jsx index 05f0d5bfa..c81283be3 100644 --- a/src/app/src/components/FacilityDetailSidebarItem.jsx +++ b/src/app/src/components/FacilityDetailSidebarItem.jsx @@ -1,8 +1,10 @@ -import React from 'react'; +import React, { useState } from 'react'; import { withStyles } from '@material-ui/core/styles'; -import Typography from '@material-ui/core/Typography'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; import FacilityDetailSidebarDetail from './FacilityDetailSidebarDetail'; +import ShowOnly from './ShowOnly'; const detailsSidebarStyles = () => Object.freeze({ @@ -10,11 +12,29 @@ const detailsSidebarStyles = () => paddingTop: '16px', }, label: { - padding: '12px 24px 4px 24px', fontSize: '0.75rem', textTransform: 'uppercase', fontWeight: 'bold', }, + primaryText: { + wordWrap: 'break-word', + }, + secondaryText: { + color: 'rgba(0, 0, 0, 0.54)', + display: 'flex', + alignItems: 'center', + fontSize: '12px', + justify: 'flex-end', + }, + divider: { + backgroundColor: 'rgba(0, 0, 0, 0.06)', + }, + icon: { + color: 'rgb(106, 106, 106)', + fontSize: '24px', + fontWeight: 300, + textAlign: 'center', + }, }); const FacilityDetailSidebarItem = ({ @@ -24,23 +44,45 @@ const FacilityDetailSidebarItem = ({ secondary, classes, embed, - href, - link, + verified, }) => { - const hasAdditionalContent = !!additionalContent?.length; + const [isOpen, setIsOpen] = useState(false); + const hasAdditionalContent = !embed && !!additionalContent?.length; + const additionalContentCount = additionalContent?.length; return (
- {label} + { + if (!hasAdditionalContent) return; + setIsOpen(!isOpen); + }} + > + + +
+ + +
+
+
{}} - link={link} - href={href} + verified={verified} /> + {isOpen && + additionalContent.map(item => ( + + ))}
); }; diff --git a/src/app/src/components/FacilityDetailSidebarLocation.jsx b/src/app/src/components/FacilityDetailSidebarLocation.jsx index cb6df6c57..ba07cbbfd 100644 --- a/src/app/src/components/FacilityDetailSidebarLocation.jsx +++ b/src/app/src/components/FacilityDetailSidebarLocation.jsx @@ -33,7 +33,12 @@ const FacilityDetailSidebarLocation = ({ data, embed }) => { label="GPS" primary={`${facilityLng}, ${facilityLat}`} secondary={attribution} - additionalContent={otherLocationsData} + embed={embed} + additionalContent={otherLocationsData.map((item, i) => ({ + primary: `${item.lng}, ${item.lat}`, + secondary: item.contributor_name, + key: `${item.lng}, ${item.lat} - ${i}`, + }))} /> ); diff --git a/src/app/src/util/constants.js b/src/app/src/util/constants.js index 4e4f3ba8e..8116c4086 100644 --- a/src/app/src/util/constants.js +++ b/src/app/src/util/constants.js @@ -715,3 +715,37 @@ export const facilitySidebarActions = { CLAIM_FACILITY: 'Claim this facility', VIEW_ON_OAR: 'View on the Open Apparel Registry', }; + +export const EXTENDED_FIELD_TYPES = [ + { + label: 'Parent Company', + fieldName: 'parent_company', + formatValue: v => v, + }, + { + label: 'Facility Type', + fieldName: 'facility_type', + formatValue: v => v, + }, + { + label: 'Product Type', + fieldName: 'product_type', + formatValue: v => v, + }, + { + label: 'Processing Type', + fieldName: 'processing_type', + formatValue: v => v, + }, + { + label: 'Number of Workers', + fieldName: 'number_of_workers', + formatValue: ({ min, max }) => + max === min ? `${max} workers` : `${min}-${max} workers`, + }, + { + label: 'Native Language Name', + fieldName: 'native_language_name', + formatValue: v => v, + }, +];