From 3b70f94e45de0940186122ae4fe3a03c6e76d02d Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Fri, 14 Feb 2020 13:15:56 -0800 Subject: [PATCH 01/19] feat: initial functionality - not complete --- src/components/App.js | 78 ++++----- src/components/Item/VisualizationItem/Item.js | 15 +- .../Item/VisualizationItem/NewItemHeader.js | 154 ++++++++++++++++++ .../Item/VisualizationItem/assets/icons.js | 31 ++++ .../styles/NewItemHeader.module.css | 27 +++ src/modules/itemTypes.js | 24 ++- 6 files changed, 284 insertions(+), 45 deletions(-) create mode 100644 src/components/Item/VisualizationItem/NewItemHeader.js create mode 100644 src/components/Item/VisualizationItem/assets/icons.js create mode 100644 src/components/Item/VisualizationItem/styles/NewItemHeader.module.css diff --git a/src/components/App.js b/src/components/App.js index 509a8b9b7..46c53371d 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import i18n from '@dhis2/d2-i18n'; import { HeaderBar } from '@dhis2/ui-widgets'; +import { CssVariables } from '@dhis2/ui-core'; import { EDIT, VIEW, NEW } from './Dashboard/dashboardModes'; import { acReceivedUser } from '../actions/user'; @@ -30,44 +31,47 @@ export class App extends Component { render() { return ( -
-
- + <> + +
+
+ +
+ + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + +
- - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - - -
+ ); } } diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index df270147b..27fb1e3a7 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -9,7 +9,8 @@ import i18n from '@dhis2/d2-i18n'; import uniqueId from 'lodash/uniqueId'; import DefaultPlugin from './DefaultPlugin'; -import ItemHeader, { HEADER_HEIGHT } from '../ItemHeader'; +import { HEADER_HEIGHT } from '../ItemHeader'; +import NewItemHeader from './NewItemHeader'; import ItemFooter from './ItemFooter'; import VisualizationItemHeaderButtons from './ItemHeaderButtons'; import * as pluginManager from './plugin'; @@ -283,11 +284,19 @@ export class Item extends Component { return ( - + {/* + /> */}
{ + const [anchorEl, setAnchorEl] = useState(null); + + const { + item, + visualization, + onSelectActiveType, + d2, + editMode, + activeType, + } = props; + + // if (isSingleValue(visualization.type)) { + // return null; + // } + + const onViewTable = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_REPORT : REPORT_TABLE + ); + handleClose(); + }; + + const onViewChart = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_CHART : CHART + ); + handleClose(); + }; + + const onViewMap = () => { + onSelectActiveType(MAP); + handleClose(); + }; + + // let disabled = false; + // if (item.type === CHART) { + // if (extractFavorite(item).type.match(/^YEAR_OVER_YEAR/)) { + // disabled = true; + // } + // } + + const handleClick = (_, event) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> +
+

{'Title goes here'}

+ + {!editMode && pluginIsAvailable(item, visualization) ? ( +
+ + + + + + {hasMapView(item.type) ? ( + + ) : null} + + + + +
+ ) : null} +
+ + ); +}; + +NewItemHeader.propTypes = { + activeType: PropTypes.string, + d2: PropTypes.object, + editMode: PropTypes.bool, + item: PropTypes.object, + visualization: PropTypes.object, + onSelectActiveType: PropTypes.func, +}; + +export default NewItemHeader; diff --git a/src/components/Item/VisualizationItem/assets/icons.js b/src/components/Item/VisualizationItem/assets/icons.js new file mode 100644 index 000000000..ea25065f8 --- /dev/null +++ b/src/components/Item/VisualizationItem/assets/icons.js @@ -0,0 +1,31 @@ +import React from 'react'; + +export const ThreeDots = () => ( + + + +); + +export const SpeechBubble = () => ( + + + +); diff --git a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css new file mode 100644 index 000000000..0d2d17fae --- /dev/null +++ b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css @@ -0,0 +1,27 @@ +.itemWrap { + padding: var(--spacers-dp8); +} + +.itemHeaderWrap { + display: flex; + align-items: top; + margin-bottom: var(--spacers-dp8); +} + +.itemActionsWrap { + margin-left: auto; + flex-shrink: 0; +} +.itemActionsWrap button { + margin-left: var(--spacers-dp8); +} + +.itemTitle { + font-weight: 500; + color: var(--colors-grey800); + font-size: 14px; + line-height: 19px; + padding: 0; + margin: 0; + align-self: center; +} diff --git a/src/modules/itemTypes.js b/src/modules/itemTypes.js index 117e541e0..1ea91f3d9 100644 --- a/src/modules/itemTypes.js +++ b/src/modules/itemTypes.js @@ -46,8 +46,22 @@ export const hasMapView = itemType => export const isTrackerDomainType = itemType => itemTypeMap[itemType].domainType === DOMAIN_TYPE_TRACKER; +export const getDefaultItemCount = itemType => + itemTypeMap[itemType].defaultItemCount || 5; + +export const getAppName = itemType => itemTypeMap[itemType].appName || ''; + // Item type map export const itemTypeMap = { + [VISUALIZATION]: { + id: VISUALIZATION, + endPointName: 'visualizations', + propName: 'visualization', + pluralTitle: i18n.t('Visualizations'), + appUrl: id => `dhis-web-data-visualizer/#/${id}`, + appName: 'Visualizer', + defaultItemCount: 10, + }, [REPORT_TABLE]: { id: REPORT_TABLE, endPointName: 'reportTables', @@ -56,7 +70,7 @@ export const itemTypeMap = { domainType: DOMAIN_TYPE_AGGREGATE, isVisualizationType: true, appUrl: id => `dhis-web-data-visualizer/#/${id}`, - appName: i18n.t('Visualizer'), + appName: 'Visualizer', }, [CHART]: { id: CHART, @@ -66,7 +80,7 @@ export const itemTypeMap = { domainType: DOMAIN_TYPE_AGGREGATE, isVisualizationType: true, appUrl: id => `dhis-web-data-visualizer/#/${id}`, - appName: i18n.t('Visualizer'), + appName: 'Visualizer', }, [MAP]: { id: MAP, @@ -76,7 +90,7 @@ export const itemTypeMap = { domainType: DOMAIN_TYPE_AGGREGATE, isVisualizationType: true, appUrl: id => `dhis-web-maps/?id=${id}`, - appName: i18n.t('Maps'), + appName: 'Maps', }, [EVENT_REPORT]: { id: EVENT_REPORT, @@ -86,7 +100,7 @@ export const itemTypeMap = { domainType: DOMAIN_TYPE_TRACKER, isVisualizationType: true, appUrl: id => `dhis-web-event-reports/?id=${id}`, - appName: i18n.t('Event Reports'), + appName: 'Event Reports', }, [EVENT_CHART]: { id: EVENT_CHART, @@ -96,7 +110,7 @@ export const itemTypeMap = { domainType: DOMAIN_TYPE_TRACKER, isVisualizationType: true, appUrl: id => `dhis-web-event-visualizer/?id=${id}`, - appName: i18n.t('Event Visualizer'), + appName: 'Event Visualizer', }, [APP]: { endPointName: 'apps', From 91295489aa9d50607b7bb7d092ce8de01c24a0b0 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Mon, 17 Feb 2020 06:31:17 -0800 Subject: [PATCH 02/19] feat: new header component --- .../Item/VisualizationItem/NewItemHeader.js | 59 +++++++++++-------- .../styles/NewItemHeader.module.css | 6 ++ 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/src/components/Item/VisualizationItem/NewItemHeader.js b/src/components/Item/VisualizationItem/NewItemHeader.js index 1a4a6c301..7382c344c 100644 --- a/src/components/Item/VisualizationItem/NewItemHeader.js +++ b/src/components/Item/VisualizationItem/NewItemHeader.js @@ -4,9 +4,13 @@ import Popover from '@material-ui/core/Popover'; import { isSingleValue } from '@dhis2/analytics'; import { Button, Menu, MenuItem, Divider } from '@dhis2/ui-core'; import i18n from '@dhis2/d2-i18n'; +import TableIcon from '@material-ui/icons/ViewList'; +import ChartIcon from '@material-ui/icons/InsertChart'; +import MapIcon from '@material-ui/icons/Public'; +import LaunchIcon from '@material-ui/icons/Launch'; import { ThreeDots } from './assets/icons.js'; -import { pluginIsAvailable, getLink } from './plugin'; +import { pluginIsAvailable, getLink, getName } from './plugin'; import { CHART, REPORT_TABLE, @@ -55,12 +59,15 @@ const NewItemHeader = props => { handleClose(); }; - // let disabled = false; - // if (item.type === CHART) { - // if (extractFavorite(item).type.match(/^YEAR_OVER_YEAR/)) { - // disabled = true; - // } - // } + const hasTableView = () => { + const type = visualization.type || item.type; + return !type.match(/^YEAR_OVER_YEAR/); + }; + + const itemHasMapView = () => { + const type = visualization.type || item.type; + return hasMapView(item.type) && !type.match(/^YEAR_OVER_YEAR/); + }; const handleClick = (_, event) => { setAnchorEl(event.currentTarget); @@ -73,7 +80,7 @@ const NewItemHeader = props => { return ( <>
-

{'Title goes here'}

+

{getName(item)}

{!editMode && pluginIsAvailable(item, visualization) ? (
@@ -101,32 +108,34 @@ const NewItemHeader = props => { } /> - - {hasMapView(item.type) ? ( + {hasTableView() && ( + } + /> + )} + {itemHasMapView() && ( } /> - ) : null} + )} + } label={i18n.t( `View in ${getAppName(item.type)} app` )} diff --git a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css index 0d2d17fae..c58a30738 100644 --- a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css +++ b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css @@ -25,3 +25,9 @@ margin: 0; align-self: center; } + +.launchIcon { + width: 16px; + height: 16px; + fill: colors.grey600; +} From 22cb332015e8094564371c386f429e7456bf6795 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Wed, 19 Feb 2020 12:25:47 -0800 Subject: [PATCH 03/19] feat: item header features --- i18n/en.pot | 26 +++++++++------- package.json | 2 +- src/components/Item/VisualizationItem/Item.js | 30 ++++--------------- .../Item/VisualizationItem/NewItemHeader.js | 21 ++++++++++++- .../styles/NewItemHeader.module.css | 3 +- yarn.lock | 9 ++++++ 6 files changed, 54 insertions(+), 37 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index e33f84d5d..ad1c93ffe 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-02-09T01:43:57.112Z\n" -"PO-Revision-Date: 2020-02-09T01:43:57.112Z\n" +"POT-Creation-Date: 2020-02-19T18:25:41.480Z\n" +"PO-Revision-Date: 2020-02-19T18:25:41.480Z\n" msgid "Dashboard" msgstr "" @@ -88,6 +88,18 @@ msgstr "" msgid "No data to display" msgstr "" +msgid "View as" +msgstr "" + +msgid "Chart" +msgstr "" + +msgid "Table" +msgstr "" + +msgid "Map" +msgstr "" + msgid "Confirm" msgstr "" @@ -127,10 +139,10 @@ msgstr "" msgid "Share" msgstr "" -msgid "Pivot tables" +msgid "Visualizations" msgstr "" -msgid "Visualizer" +msgid "Pivot tables" msgstr "" msgid "Charts" @@ -142,15 +154,9 @@ msgstr "" msgid "Event reports" msgstr "" -msgid "Event Reports" -msgstr "" - msgid "Event charts" msgstr "" -msgid "Event Visualizer" -msgstr "" - msgid "Apps" msgstr "" diff --git a/package.json b/package.json index 1f0248d51..f1c6bedee 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@dhis2/d2-ui-translation-dialog": "^6.0.1", "@dhis2/data-visualizer-plugin": "^34.2.1", "@dhis2/prop-types": "^1.2.1", - "@dhis2/ui-core": "^4.9.1", + "@dhis2/ui-core": "^4.11.0", "@dhis2/ui-widgets": "^2.0.5", "@material-ui/core": "^3.9.2", "@material-ui/icons": "^3.0.2", diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index 27fb1e3a7..a1eccb380 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -9,10 +9,8 @@ import i18n from '@dhis2/d2-i18n'; import uniqueId from 'lodash/uniqueId'; import DefaultPlugin from './DefaultPlugin'; -import { HEADER_HEIGHT } from '../ItemHeader'; -import NewItemHeader from './NewItemHeader'; +import ItemHeader from './NewItemHeader'; import ItemFooter from './ItemFooter'; -import VisualizationItemHeaderButtons from './ItemHeaderButtons'; import * as pluginManager from './plugin'; import { sGetVisualization } from '../../../reducers/visualizations'; import { sGetItemFiltersRoot } from '../../../reducers/itemFilters'; @@ -31,6 +29,8 @@ import { colors } from '@dhis2/ui-core'; import memoizeOne from '../../../modules/memoizeOne'; import { getVisualizationConfig } from './plugin'; +const HEADER_HEIGHT = 45; + const styles = { icon: { width: 16, @@ -252,21 +252,6 @@ export class Item extends Component { ); }; - getActionButtons = () => - pluginManager.pluginIsAvailable( - this.props.item, - this.props.visualization - ) && !this.props.editMode ? ( - - ) : null; - getContentStyle = () => { const { item, editMode } = this.props; const PADDING_BOTTOM = 4; @@ -284,19 +269,16 @@ export class Item extends Component { return ( - - {/* */}
{ {!editMode && pluginIsAvailable(item, visualization) ? (
+ @@ -108,6 +111,7 @@ const NewItemHeader = props => { } @@ -115,6 +119,10 @@ const NewItemHeader = props => { {hasTableView() && ( } @@ -123,6 +131,7 @@ const NewItemHeader = props => { {itemHasMapView() && ( } @@ -142,6 +151,15 @@ const NewItemHeader = props => { href={getLink(item, d2)} target="_blank" /> + } + label={i18n.t( + `View interpretations and details` + )} + onClick={props.onToggleFooter} + />
@@ -158,6 +176,7 @@ NewItemHeader.propTypes = { item: PropTypes.object, visualization: PropTypes.object, onSelectActiveType: PropTypes.func, + onToggleFooter: PropTypes.func, }; export default NewItemHeader; diff --git a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css index c58a30738..73d0ac1af 100644 --- a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css +++ b/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css @@ -5,7 +5,8 @@ .itemHeaderWrap { display: flex; align-items: top; - margin-bottom: var(--spacers-dp8); + margin: var(--spacers-dp4) var(--spacers-dp4) var(--spacers-dp8) + var(--spacers-dp4); } .itemActionsWrap { diff --git a/yarn.lock b/yarn.lock index eb6cd4555..393e625e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1433,6 +1433,15 @@ dependencies: prop-types "^15" +"@dhis2/ui-core@^4.11.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@dhis2/ui-core/-/ui-core-4.11.0.tgz#2b629f3f2dd8c9d94e667bc8a4230cc90d1687e6" + integrity sha512-9vJXiWUbwrbOmJAhn275YeZOs1i+dyXAx5lM3I10l3YnqE1YpZ5QkLzjfsheNs0FsAttU6IkHSw7DzNSzDleRA== + dependencies: + "@dhis2/prop-types" "^1.5.0" + "@popperjs/core" "^2.0.6" + classnames "^2.2.6" + "@dhis2/ui-core@^4.9.1": version "4.9.1" resolved "https://registry.yarnpkg.com/@dhis2/ui-core/-/ui-core-4.9.1.tgz#13ebaa43deceb5c3fa2955668c183131241847fa" From efbf12fba395dcb6337758f102a0a7ae60c2b253 Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Wed, 19 Feb 2020 13:31:54 -0800 Subject: [PATCH 04/19] feat: adding more features, yay --- .../Item/VisualizationItem/NewItemHeader.js | 92 +++++++++++-------- .../styles/NewItemHeader.module.css | 6 -- 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/src/components/Item/VisualizationItem/NewItemHeader.js b/src/components/Item/VisualizationItem/NewItemHeader.js index 0da2eec6e..606bde122 100644 --- a/src/components/Item/VisualizationItem/NewItemHeader.js +++ b/src/components/Item/VisualizationItem/NewItemHeader.js @@ -73,18 +73,65 @@ const NewItemHeader = props => { setAnchorEl(event.currentTarget); }; + const handleInterpretationClick = () => { + props.onToggleFooter(); + if (anchorEl !== null) { + handleClose(); + } + }; + const handleClose = () => { setAnchorEl(null); }; + const canViewAs = !isSingleValue(props.visualization.type); + + const ViewAsMenuItems = () => { + return ( + <> + } + /> + {hasTableView() && ( + } + /> + )} + {itemHasMapView() && ( + } + /> + )} + + + ); + }; + return ( <>

{getName(item)}

- {!editMode && pluginIsAvailable(item, visualization) ? (
- - { {canViewAs ? ( <> - - - } - /> - {hasTableView() && ( - } - /> - )} - {itemHasMapView() && ( - } - /> - )} - - + ) : null} @@ -212,9 +175,7 @@ const NewItemHeader = props => { } - label={i18n.t( - `View interpretations and details` - )} + label={interpretationMenuLabel} onClick={handleInterpretationClick} /> @@ -227,6 +188,7 @@ const NewItemHeader = props => { }; NewItemHeader.propTypes = { + activeFooter: PropTypes.bool, activeType: PropTypes.string, d2: PropTypes.object, editMode: PropTypes.bool, From 8e3b1c304f2c0006292c8c01bc03a0f9538774db Mon Sep 17 00:00:00 2001 From: Jen Jones Arnesen Date: Sun, 23 Feb 2020 21:44:51 -0800 Subject: [PATCH 07/19] fix: in progress --- i18n/en.pot | 30 +- src/components/Item/DeleteItemButton.js | 23 ++ src/components/Item/ItemHeader.js | 228 ++++++++++- src/components/Item/ItemHeaderButton.js | 27 -- src/components/Item/VisualizationItem/Item.js | 2 +- .../VisualizationItem/ItemHeaderButtons.js | 354 +++++++++--------- .../Item/VisualizationItem/NewItemHeader.js | 201 ---------- .../__tests__/ItemHeaderButtons.spec.js | 24 -- .../styles/ItemHeaderButtons.module.css | 7 + .../{VisualizationItem => }/assets/icons.js | 0 .../DeleteItemButton.module.css} | 4 +- .../ItemHeader.module.css} | 8 - src/components/ItemGrid/DeleteItemButton.js | 29 -- src/components/ItemGrid/ItemGrid.css | 20 - src/components/ItemGrid/ItemGrid.js | 9 +- 15 files changed, 449 insertions(+), 517 deletions(-) create mode 100644 src/components/Item/DeleteItemButton.js delete mode 100644 src/components/Item/ItemHeaderButton.js delete mode 100644 src/components/Item/VisualizationItem/NewItemHeader.js delete mode 100644 src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js create mode 100644 src/components/Item/VisualizationItem/styles/ItemHeaderButtons.module.css rename src/components/Item/{VisualizationItem => }/assets/icons.js (100%) rename src/components/Item/{ItemHeaderButton.css => styles/DeleteItemButton.module.css} (64%) rename src/components/Item/{VisualizationItem/styles/NewItemHeader.module.css => styles/ItemHeader.module.css} (74%) delete mode 100644 src/components/ItemGrid/DeleteItemButton.js diff --git a/i18n/en.pot b/i18n/en.pot index 9e8e605ed..90215b778 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-02-23T20:45:31.827Z\n" -"PO-Revision-Date: 2020-02-23T20:45:31.827Z\n" +"POT-Creation-Date: 2020-02-24T05:43:02.589Z\n" +"PO-Revision-Date: 2020-02-24T05:43:02.589Z\n" msgid "Dashboard" msgstr "" @@ -64,43 +64,43 @@ msgstr[1] "" msgid "Remove" msgstr "" -msgid "Messages" +msgid "View as Chart" msgstr "" -msgid "See all messages" +msgid "View as Table" msgstr "" -msgid "Spacer" +msgid "View as Map" msgstr "" -msgid "Use a spacer to create empty vertical space between other dashboard items." +msgid "Hide interpretations and details" msgstr "" -msgid "Text item" +msgid "View interpretations and details" msgstr "" -msgid "Add text here" +msgid "Messages" msgstr "" -msgid "Unable to load the plugin for this item" +msgid "See all messages" msgstr "" -msgid "No data to display" +msgid "Spacer" msgstr "" -msgid "View as Chart" +msgid "Use a spacer to create empty vertical space between other dashboard items." msgstr "" -msgid "View as Table" +msgid "Text item" msgstr "" -msgid "View as Map" +msgid "Add text here" msgstr "" -msgid "Hide interpretations and details" +msgid "Unable to load the plugin for this item" msgstr "" -msgid "View interpretations and details" +msgid "No data to display" msgstr "" msgid "Confirm" diff --git a/src/components/Item/DeleteItemButton.js b/src/components/Item/DeleteItemButton.js new file mode 100644 index 000000000..087bcfade --- /dev/null +++ b/src/components/Item/DeleteItemButton.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DeleteIcon from '@material-ui/icons/Delete'; +import { colors } from '@dhis2/ui-core'; + +import classes from './styles/DeleteItemButton.module.css'; + +const DeleteItemHeaderButton = ({ onClick }) => ( + +); + +DeleteItemHeaderButton.propTypes = { + onClick: PropTypes.func, +}; + +export default DeleteItemHeaderButton; diff --git a/src/components/Item/ItemHeader.js b/src/components/Item/ItemHeader.js index 3e16374ef..759769c70 100644 --- a/src/components/Item/ItemHeader.js +++ b/src/components/Item/ItemHeader.js @@ -1,28 +1,228 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; -export const HEADER_HEIGHT = 45; +import Popover from '@material-ui/core/Popover'; +import { isSingleValue } from '@dhis2/analytics'; +import { Button, Menu, MenuItem, Divider } from '@dhis2/ui-core'; +import i18n from '@dhis2/d2-i18n'; +import TableIcon from '@material-ui/icons/ViewList'; +import ChartIcon from '@material-ui/icons/InsertChart'; +import MapIcon from '@material-ui/icons/Public'; +import LaunchIcon from '@material-ui/icons/Launch'; + +import { acRemoveDashboardItem } from '../../actions/editDashboard'; +import DeleteItemButton from './DeleteItemButton'; +import { ThreeDots, SpeechBubble } from './assets/icons'; +import { + pluginIsAvailable, + getLink, + getName, +} from './VisualizationItem/plugin'; +import { + CHART, + REPORT_TABLE, + MAP, + EVENT_REPORT, + EVENT_CHART, + isTrackerDomainType, + hasMapView, + getAppName, +} from '../../modules/itemTypes'; + +import classes from './styles/ItemHeader.module.css'; const ItemHeader = props => { - const { title, actionButtons, editMode } = props; + const [anchorEl, setAnchorEl] = useState(null); + + const { + item, + visualization, + onSelectActiveType, + d2, + editMode, + activeType, + acRemoveDashboardItem, + } = props; + + const onViewTable = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_REPORT : REPORT_TABLE + ); + handleClose(); + }; + + const onViewChart = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_CHART : CHART + ); + handleClose(); + }; + + const onViewMap = () => { + onSelectActiveType(MAP); + handleClose(); + }; + + const itemHasTableView = () => { + const type = visualization.type || item.type; + return !type.match(/^YEAR_OVER_YEAR/); + }; + + const itemHasMapView = () => { + const type = visualization.type || item.type; + return hasMapView(item.type) && !type.match(/^YEAR_OVER_YEAR/); + }; + + const handleMenuClick = (_, event) => { + setAnchorEl(event.currentTarget); + }; + + const handleInterpretationClick = () => { + props.onToggleFooter(); + if (anchorEl !== null) { + handleClose(); + } + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const canViewAs = !isSingleValue(props.visualization.type); + + const ViewAsMenuItems = () => { + return ( + <> + } + /> + {itemHasTableView() && ( + } + /> + )} + {itemHasMapView() && ( + } + /> + )} + + ); + }; + + const ViewModeActions = () => { + return ( + <> + + + + + {canViewAs ? ( + <> + + + + ) : null} + } + label={i18n.t( + `View in ${getAppName(item.type)} app` + )} + href={getLink(item, d2)} + target="_blank" + /> + } + label={interpretationMenuLabel} + onClick={handleInterpretationClick} + /> + + + + ); + }; + + const handleDeleteItem = () => acRemoveDashboardItem(item.id); + + const interpretationMenuLabel = props.activeFooter + ? i18n.t(`Hide interpretations and details`) + : i18n.t(`View interpretations and details`); return ( -
-
- {title} + <> +
+

{getName(item)}

+
+ {!editMode && pluginIsAvailable(item, visualization) && ( + + )} + {editMode && ( + + )} +
- {actionButtons} -
+ ); }; ItemHeader.propTypes = { - actionButtons: PropTypes.node, + acRemoveDashboardItem: PropTypes.func, + activeFooter: PropTypes.bool, + activeType: PropTypes.string, + d2: PropTypes.object, editMode: PropTypes.bool, - title: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), + item: PropTypes.object, + title: PropTypes.string, + visualization: PropTypes.object, + onSelectActiveType: PropTypes.func, + onToggleFooter: PropTypes.func, +}; + +const mapDispatchToProps = { + acRemoveDashboardItem, }; -export default ItemHeader; +export default connect( + null, + mapDispatchToProps +)(ItemHeader); diff --git a/src/components/Item/ItemHeaderButton.js b/src/components/Item/ItemHeaderButton.js deleted file mode 100644 index 4bbae6090..000000000 --- a/src/components/Item/ItemHeaderButton.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -import './ItemHeaderButton.css'; - -const ItemHeaderButton = ({ disabled, onClick, children, style }) => { - return ( - - ); -}; - -ItemHeaderButton.propTypes = { - children: PropTypes.node, - disabled: PropTypes.bool, - style: PropTypes.object, - onClick: PropTypes.func, -}; - -export default ItemHeaderButton; diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js index a1eccb380..ea16e2114 100644 --- a/src/components/Item/VisualizationItem/Item.js +++ b/src/components/Item/VisualizationItem/Item.js @@ -9,7 +9,7 @@ import i18n from '@dhis2/d2-i18n'; import uniqueId from 'lodash/uniqueId'; import DefaultPlugin from './DefaultPlugin'; -import ItemHeader from './NewItemHeader'; +import ItemHeader from '../ItemHeader'; import ItemFooter from './ItemFooter'; import * as pluginManager from './plugin'; import { sGetVisualization } from '../../../reducers/visualizations'; diff --git a/src/components/Item/VisualizationItem/ItemHeaderButtons.js b/src/components/Item/VisualizationItem/ItemHeaderButtons.js index 18dd6e1f1..ddb14ef54 100644 --- a/src/components/Item/VisualizationItem/ItemHeaderButtons.js +++ b/src/components/Item/VisualizationItem/ItemHeaderButtons.js @@ -1,192 +1,210 @@ -import React, { Component, Fragment } from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import MessageIcon from '@material-ui/icons/Message'; +import { connect } from 'react-redux'; + +import Popover from '@material-ui/core/Popover'; +import { isSingleValue } from '@dhis2/analytics'; +import { Button, Menu, MenuItem, Divider } from '@dhis2/ui-core'; +import i18n from '@dhis2/d2-i18n'; import TableIcon from '@material-ui/icons/ViewList'; import ChartIcon from '@material-ui/icons/InsertChart'; import MapIcon from '@material-ui/icons/Public'; +import LaunchIcon from '@material-ui/icons/Launch'; -import { extractFavorite } from './plugin'; -import ItemHeaderButton from '../ItemHeaderButton'; +import { acRemoveDashboardItem } from '../../actions/editDashboard'; +import DeleteItemButton from './DeleteItemButton'; +import { ThreeDots, SpeechBubble } from './assets/icons'; +import { pluginIsAvailable, getLink } from './VisualizationItem/plugin'; import { CHART, - MAP, REPORT_TABLE, - EVENT_CHART, + MAP, EVENT_REPORT, + EVENT_CHART, isTrackerDomainType, hasMapView, -} from '../../../modules/itemTypes'; -import { colors, theme } from '@dhis2/ui-core'; -import { isSingleValue } from '@dhis2/analytics'; - -const style = { - iconBase: { - width: '24px', - height: '24px', - fill: colors.grey500, - }, - buttonBase: { - padding: '5px 6px 3px 6px', - }, - buttonDisabled: { - padding: '5px 6px 3px 6px', - opacity: 0.5, - cursor: 'unset', - }, - toggleFooterPadding: { - padding: '7px 6px 1px 6px', - }, - border: { - borderRadius: '2px', - border: `1px solid ${colors.grey200}`, - }, -}; - -const baseStyle = { - icon: style.iconBase, - container: style.buttonBase, -}; - -const disabledStyle = { - icon: style.iconBase, - container: style.buttonDisabled, -}; - -const activeStyle = { - icon: { ...style.iconBase, fill: theme.primary800 }, - container: { - ...style.buttonBase, - backgroundColor: theme.primary100, - }, -}; - -const inactiveStyle = disabled => (disabled ? disabledStyle : baseStyle); - -const tableBtnStyle = (activeType, disabled) => - [REPORT_TABLE, EVENT_REPORT].includes(activeType) - ? activeStyle - : inactiveStyle(disabled); - -const chartBtnStyle = (activeType, disabled) => - [CHART, EVENT_CHART].includes(activeType) - ? activeStyle - : inactiveStyle(disabled); - -const mapBtnStyle = (activeType, disabled) => - [MAP].includes(activeType) ? activeStyle : inactiveStyle(disabled); - -class VisualizationItemHeaderButtons extends Component { - renderInterpretationButton() { - const { activeFooter, onToggleFooter } = this.props; - - const toggleFooterBase = activeFooter ? activeStyle : baseStyle; - - const toggleFooter = { - ...toggleFooterBase, - container: { - ...toggleFooterBase.container, - ...style.toggleFooterPadding, - ...style.border, - }, - }; - - return ( - - - - - + getAppName, +} from '../../modules/itemTypes'; + +import classes from './styles/ItemHeaderButtons.module.css'; + +const ItemHeaderButtons = props => { + const [anchorEl, setAnchorEl] = useState(null); + + const { + item, + visualization, + onSelectActiveType, + d2, + editMode, + activeType, + acRemoveDashboardItem, + } = props; + + const onViewTable = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_REPORT : REPORT_TABLE ); - } - - renderVisualizationButtons() { - const { - item, - visualization, - onSelectActiveType, - activeType, - } = this.props; - - if (isSingleValue(visualization.type)) { - return null; - } - - const onViewTable = () => - onSelectActiveType( - isTrackerDomainType(item.type) ? EVENT_REPORT : REPORT_TABLE - ); - - const onViewChart = () => - onSelectActiveType( - isTrackerDomainType(item.type) ? EVENT_CHART : CHART - ); - - const onViewMap = () => onSelectActiveType(MAP); - - // disable toggle buttons - let disabled = false; + handleClose(); + }; - if (item.type === CHART) { - if (extractFavorite(item).type.match(/^YEAR_OVER_YEAR/)) { - disabled = true; - } + const onViewChart = () => { + onSelectActiveType( + isTrackerDomainType(item.type) ? EVENT_CHART : CHART + ); + handleClose(); + }; + + const onViewMap = () => { + onSelectActiveType(MAP); + handleClose(); + }; + + const itemHasTableView = () => { + const type = visualization.type || item.type; + return !type.match(/^YEAR_OVER_YEAR/); + }; + + const itemHasMapView = () => { + const type = visualization.type || item.type; + return hasMapView(item.type) && !type.match(/^YEAR_OVER_YEAR/); + }; + + const handleMenuClick = (_, event) => { + setAnchorEl(event.currentTarget); + }; + + const handleInterpretationClick = () => { + props.onToggleFooter(); + if (anchorEl !== null) { + handleClose(); } - - const tableButtonStyle = tableBtnStyle(activeType, disabled); - const chartButtonStyle = chartBtnStyle(activeType, disabled); - const mapButtonStyle = mapBtnStyle(activeType, disabled); - - return ( -
-
- - - - - - - {hasMapView(item.type) ? ( - - - - ) : null} -
+ }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const canViewAs = !isSingleValue(props.visualization.type); + + const ViewAsMenuItems = () => ( + <> + } + /> + {itemHasTableView() && ( + } + /> + )} + {itemHasMapView() && ( + } + /> + )} + + ); + + const ViewModeActions = () => ( + <> + + + + + {canViewAs && ( + <> + + + + )} + } + label={i18n.t(`View in ${getAppName(item.type)} app`)} + href={getLink(item, d2)} + target="_blank" + /> + } + label={interpretationMenuLabel} + onClick={handleInterpretationClick} + /> + + + + ); + + const handleDeleteItem = () => acRemoveDashboardItem(item.id); + + const interpretationMenuLabel = props.activeFooter + ? i18n.t(`Hide interpretations and details`) + : i18n.t(`View interpretations and details`); + + return ( + <> +
+ {!editMode && pluginIsAvailable(item, visualization) && ( + + )} + {editMode && }
- ); - } - - render() { - return ( - - {this.renderInterpretationButton()} - {this.renderVisualizationButtons()} - - ); - } -} + + ); +}; -VisualizationItemHeaderButtons.propTypes = { +ItemHeaderButtons.propTypes = { + acRemoveDashboardItem: PropTypes.func, activeFooter: PropTypes.bool, activeType: PropTypes.string, + d2: PropTypes.object, + editMode: PropTypes.bool, item: PropTypes.object, visualization: PropTypes.object, onSelectActiveType: PropTypes.func, onToggleFooter: PropTypes.func, }; -export default VisualizationItemHeaderButtons; +const mapDispatchToProps = { + acRemoveDashboardItem, +}; + +export default connect( + null, + mapDispatchToProps +)(ItemHeaderButtons); diff --git a/src/components/Item/VisualizationItem/NewItemHeader.js b/src/components/Item/VisualizationItem/NewItemHeader.js deleted file mode 100644 index 8fbc55a97..000000000 --- a/src/components/Item/VisualizationItem/NewItemHeader.js +++ /dev/null @@ -1,201 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import Popover from '@material-ui/core/Popover'; -import { isSingleValue } from '@dhis2/analytics'; -import { Button, Menu, MenuItem, Divider } from '@dhis2/ui-core'; -import i18n from '@dhis2/d2-i18n'; -import TableIcon from '@material-ui/icons/ViewList'; -import ChartIcon from '@material-ui/icons/InsertChart'; -import MapIcon from '@material-ui/icons/Public'; -import LaunchIcon from '@material-ui/icons/Launch'; - -import { ThreeDots, SpeechBubble } from './assets/icons.js'; -import { pluginIsAvailable, getLink, getName } from './plugin'; -import { - CHART, - REPORT_TABLE, - MAP, - EVENT_REPORT, - EVENT_CHART, - isTrackerDomainType, - hasMapView, - getAppName, -} from '../../../modules/itemTypes'; - -import classes from './styles/NewItemHeader.module.css'; - -const NewItemHeader = props => { - const [anchorEl, setAnchorEl] = useState(null); - - const { - item, - visualization, - onSelectActiveType, - d2, - editMode, - activeType, - } = props; - - // if (isSingleValue(visualization.type)) { - // return null; - // } - - const onViewTable = () => { - onSelectActiveType( - isTrackerDomainType(item.type) ? EVENT_REPORT : REPORT_TABLE - ); - handleClose(); - }; - - const onViewChart = () => { - onSelectActiveType( - isTrackerDomainType(item.type) ? EVENT_CHART : CHART - ); - handleClose(); - }; - - const onViewMap = () => { - onSelectActiveType(MAP); - handleClose(); - }; - - const hasTableView = () => { - const type = visualization.type || item.type; - return !type.match(/^YEAR_OVER_YEAR/); - }; - - const itemHasMapView = () => { - const type = visualization.type || item.type; - return hasMapView(item.type) && !type.match(/^YEAR_OVER_YEAR/); - }; - - const handleMenuClick = (_, event) => { - setAnchorEl(event.currentTarget); - }; - - const handleInterpretationClick = () => { - props.onToggleFooter(); - if (anchorEl !== null) { - handleClose(); - } - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const canViewAs = !isSingleValue(props.visualization.type); - - const ViewAsMenuItems = () => { - return ( - <> - } - /> - {hasTableView() && ( - } - /> - )} - {itemHasMapView() && ( - } - /> - )} - - ); - }; - - const interpretationMenuLabel = props.activeFooter - ? i18n.t(`Hide interpretations and details`) - : i18n.t(`View interpretations and details`); - - return ( - <> -
-

{getName(item)}

- {!editMode && pluginIsAvailable(item, visualization) ? ( -
- - - - - {canViewAs ? ( - <> - - - - ) : null} - } - label={i18n.t( - `View in ${getAppName(item.type)} app` - )} - href={getLink(item, d2)} - target="_blank" - /> - } - label={interpretationMenuLabel} - onClick={handleInterpretationClick} - /> - - -
- ) : null} -
- - ); -}; - -NewItemHeader.propTypes = { - activeFooter: PropTypes.bool, - activeType: PropTypes.string, - d2: PropTypes.object, - editMode: PropTypes.bool, - item: PropTypes.object, - visualization: PropTypes.object, - onSelectActiveType: PropTypes.func, - onToggleFooter: PropTypes.func, -}; - -export default NewItemHeader; diff --git a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js b/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js deleted file mode 100644 index a50f441c4..000000000 --- a/src/components/Item/VisualizationItem/__tests__/ItemHeaderButtons.spec.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { shallow } from 'enzyme'; -import ItemHeaderButtons from '../ItemHeaderButtons'; - -it('renders correctly', () => { - const buttons = shallow( - - My Little Pony - - ); - expect(buttons).toMatchSnapshot(); -}); diff --git a/src/components/Item/VisualizationItem/styles/ItemHeaderButtons.module.css b/src/components/Item/VisualizationItem/styles/ItemHeaderButtons.module.css new file mode 100644 index 000000000..72e9dc045 --- /dev/null +++ b/src/components/Item/VisualizationItem/styles/ItemHeaderButtons.module.css @@ -0,0 +1,7 @@ +.itemActionsWrap { + margin-left: auto; + flex-shrink: 0; +} +.itemActionsWrap button { + margin-left: var(--spacers-dp8); +} diff --git a/src/components/Item/VisualizationItem/assets/icons.js b/src/components/Item/assets/icons.js similarity index 100% rename from src/components/Item/VisualizationItem/assets/icons.js rename to src/components/Item/assets/icons.js diff --git a/src/components/Item/ItemHeaderButton.css b/src/components/Item/styles/DeleteItemButton.module.css similarity index 64% rename from src/components/Item/ItemHeaderButton.css rename to src/components/Item/styles/DeleteItemButton.module.css index d4f16c9ed..3e8b940c4 100644 --- a/src/components/Item/ItemHeaderButton.css +++ b/src/components/Item/styles/DeleteItemButton.module.css @@ -1,9 +1,9 @@ -.item-action-button { +.deleteItemButton { border: none; background: transparent; cursor: pointer; } -.item-action-button:focus { +.deleteItemButton:focus { outline: 0; } diff --git a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css b/src/components/Item/styles/ItemHeader.module.css similarity index 74% rename from src/components/Item/VisualizationItem/styles/NewItemHeader.module.css rename to src/components/Item/styles/ItemHeader.module.css index 4b0f477ac..e794b2583 100644 --- a/src/components/Item/VisualizationItem/styles/NewItemHeader.module.css +++ b/src/components/Item/styles/ItemHeader.module.css @@ -9,14 +9,6 @@ var(--spacers-dp4); } -.itemActionsWrap { - margin-left: auto; - flex-shrink: 0; -} -.itemActionsWrap button { - margin-left: var(--spacers-dp8); -} - .itemTitle { font-weight: 500; color: var(--colors-grey800); diff --git a/src/components/ItemGrid/DeleteItemButton.js b/src/components/ItemGrid/DeleteItemButton.js deleted file mode 100644 index a0b25dd84..000000000 --- a/src/components/ItemGrid/DeleteItemButton.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ItemHeaderButton from '../Item/ItemHeaderButton'; -import DeleteIcon from '@material-ui/icons/Delete'; -import { colors } from '@dhis2/ui-core'; - -const style = { - button: { - position: 'absolute', - top: '7px', - right: '7px', - zIndex: '1000', - }, - icon: { - fill: colors.red500, - }, -}; - -const DeleteItemHeaderButton = ({ onClick }) => ( - - - -); - -DeleteItemHeaderButton.propTypes = { - onClick: PropTypes.func, -}; - -export default DeleteItemHeaderButton; diff --git a/src/components/ItemGrid/ItemGrid.css b/src/components/ItemGrid/ItemGrid.css index 2442accf1..dd4f6134f 100644 --- a/src/components/ItemGrid/ItemGrid.css +++ b/src/components/ItemGrid/ItemGrid.css @@ -48,26 +48,6 @@ background: none; } -/* dashboard item - header */ - -.dashboard-item-header { - font-size: 15px; - font-weight: 700; - color: #3a3a3a; - display: flex; - align-items: center; - padding: 0 4px 0 14px; - height: 45px; -} - -.dashboard-item-header-title { - flex: 1; - overflow: hidden; - position: relative; - top: -1px; - left: -1px; -} - /* dashboard item - content */ .dashboard-item-content { diff --git a/src/components/ItemGrid/ItemGrid.js b/src/components/ItemGrid/ItemGrid.js index beefe714d..7eab80a1f 100644 --- a/src/components/ItemGrid/ItemGrid.js +++ b/src/components/ItemGrid/ItemGrid.js @@ -22,7 +22,7 @@ import { onItemResize, } from './gridUtil'; import { orArray } from '../../modules/util'; -import DeleteItemButton from './DeleteItemButton'; + import NoContentMessage from '../../widgets/NoContentMessage'; import 'react-grid-layout/css/styles.css'; @@ -148,13 +148,6 @@ export class ItemGrid extends Component { key={item.i} className={itemClassNames} > - {edit ? ( - - ) : null} Date: Sun, 23 Feb 2020 22:47:06 -0800 Subject: [PATCH 08/19] fix: in progress --- i18n/en.pot | 30 +-- src/components/Item/AppItem/Item.js | 2 +- src/components/Item/ItemHeader.js | 217 ++---------------- src/components/Item/ListItem/Item.js | 2 +- src/components/Item/MessagesItem/Item.js | 6 +- src/components/Item/NotSupportedItem/Item.js | 5 +- src/components/Item/SpacerItem/Item.js | 9 +- src/components/Item/TextItem/Item.js | 2 +- src/components/Item/VisualizationItem/Item.js | 61 ++--- .../VisualizationItem/ItemHeaderButtons.js | 51 +--- .../{ => VisualizationItem}/assets/icons.js | 0 .../styles/ItemHeaderButtons.module.css | 7 - .../Item/styles/ItemHeader.module.css | 8 + 13 files changed, 96 insertions(+), 304 deletions(-) rename src/components/Item/{ => VisualizationItem}/assets/icons.js (100%) delete mode 100644 src/components/Item/VisualizationItem/styles/ItemHeaderButtons.module.css diff --git a/i18n/en.pot b/i18n/en.pot index 90215b778..ef85c3443 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2020-02-24T05:43:02.589Z\n" -"PO-Revision-Date: 2020-02-24T05:43:02.589Z\n" +"POT-Creation-Date: 2020-02-24T06:46:00.781Z\n" +"PO-Revision-Date: 2020-02-24T06:46:00.781Z\n" msgid "Dashboard" msgstr "" @@ -64,43 +64,43 @@ msgstr[1] "" msgid "Remove" msgstr "" -msgid "View as Chart" +msgid "Messages" msgstr "" -msgid "View as Table" +msgid "See all messages" msgstr "" -msgid "View as Map" +msgid "Spacer" msgstr "" -msgid "Hide interpretations and details" +msgid "Use a spacer to create empty vertical space between other dashboard items." msgstr "" -msgid "View interpretations and details" +msgid "Text item" msgstr "" -msgid "Messages" +msgid "Add text here" msgstr "" -msgid "See all messages" +msgid "Unable to load the plugin for this item" msgstr "" -msgid "Spacer" +msgid "No data to display" msgstr "" -msgid "Use a spacer to create empty vertical space between other dashboard items." +msgid "Hide interpretations and details" msgstr "" -msgid "Text item" +msgid "View interpretations and details" msgstr "" -msgid "Add text here" +msgid "View as Chart" msgstr "" -msgid "Unable to load the plugin for this item" +msgid "View as Table" msgstr "" -msgid "No data to display" +msgid "View as Map" msgstr "" msgid "Confirm" diff --git a/src/components/Item/AppItem/Item.js b/src/components/Item/AppItem/Item.js index bfb10f9a3..b96538f48 100644 --- a/src/components/Item/AppItem/Item.js +++ b/src/components/Item/AppItem/Item.js @@ -39,7 +39,7 @@ const AppItem = ({ item, itemFilters }, context) => { return appDetails && appDetails.name && appDetails.launchUrl ? ( - +