(
)
NotSupportedItem.propTypes = {
+ dashboardMode: PropTypes.string,
item: PropTypes.object,
}
diff --git a/src/components/Item/PageBreakItem/Item.js b/src/components/Item/PageBreakItem/Item.js
new file mode 100644
index 000000000..29b71dea9
--- /dev/null
+++ b/src/components/Item/PageBreakItem/Item.js
@@ -0,0 +1,5 @@
+import React from 'react'
+
+const PageBreakItem = () =>
+
+export default PageBreakItem
diff --git a/src/components/Item/PrintTitlePageItem/Item.js b/src/components/Item/PrintTitlePageItem/Item.js
new file mode 100644
index 000000000..d29b1da67
--- /dev/null
+++ b/src/components/Item/PrintTitlePageItem/Item.js
@@ -0,0 +1,76 @@
+import React from 'react'
+import { connect } from 'react-redux'
+import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
+
+import { orObject } from '../../../modules/util'
+
+import {
+ sGetSelectedId,
+ sGetSelectedShowDescription,
+} from '../../../reducers/selected'
+import { sGetDashboardById } from '../../../reducers/dashboards'
+import { sGetNamedItemFilters } from '../../../reducers/itemFilters'
+
+import classes from './styles/Item.module.css'
+
+const PrintTitlePageItem = ({
+ name,
+ description,
+ itemFilters,
+ showDescription,
+}) => {
+ const getItemFilterList = () => {
+ const listItems = itemFilters.map(({ name, values }) => (
+
+ {name}: {values.map(val => val.name).join(', ')}
+
+ ))
+
+ return
+ }
+
+ return (
+
+
{name}
+ {showDescription && description && (
+
{description}
+ )}
+ {itemFilters.length > 0 && (
+ <>
+
+ {i18n.t('Filters applied')}
+
+ {getItemFilterList()}
+ >
+ )}
+
+ )
+}
+
+PrintTitlePageItem.propTypes = {
+ description: PropTypes.string,
+ itemFilters: PropTypes.array,
+ name: PropTypes.string,
+ showDescription: PropTypes.bool,
+}
+
+PrintTitlePageItem.defaultProps = {
+ description: '',
+ name: '',
+ showDescription: false,
+}
+
+const mapStateToProps = state => {
+ const id = sGetSelectedId(state)
+ const dashboard = orObject(sGetDashboardById(state, id))
+
+ return {
+ name: dashboard.displayName,
+ itemFilters: sGetNamedItemFilters(state),
+ description: dashboard.displayDescription,
+ showDescription: sGetSelectedShowDescription(state),
+ }
+}
+
+export default connect(mapStateToProps)(PrintTitlePageItem)
diff --git a/src/components/Item/PrintTitlePageItem/styles/Item.module.css b/src/components/Item/PrintTitlePageItem/styles/Item.module.css
new file mode 100644
index 000000000..c0ce051fd
--- /dev/null
+++ b/src/components/Item/PrintTitlePageItem/styles/Item.module.css
@@ -0,0 +1,40 @@
+.titlePage {
+ margin-left: 30px;
+ padding-left: 10px;
+}
+
+.name {
+ font-size: 20px;
+ font-weight: 500;
+ color: var(--colors-grey700);
+ cursor: default;
+ user-select: text;
+}
+
+.description {
+ margin-right: 10px;
+ font-size: 14px;
+ color: var(--colors-grey700);
+}
+
+.filterTitle {
+ font-size: 14px;
+ color: var(--colors-grey700);
+ margin-top: 25px;
+}
+
+.filterList {
+ line-height: 18px;
+ letter-spacing: 0.1px;
+ list-style-position: outside;
+ margin: 0 0 0 var(--spacers-dp12);
+ padding: 0 0 0 var(--spacers-dp12);
+}
+
+.filterListItem {
+ font-size: 14px;
+ line-height: 18px;
+ letter-spacing: 0.1px;
+ margin-bottom: var(--spacers-dp12);
+ color: var(--colors-grey700);
+}
diff --git a/src/components/Item/SpacerItem/Item.js b/src/components/Item/SpacerItem/Item.js
index 2f3a9e26b..ae920e924 100644
--- a/src/components/Item/SpacerItem/Item.js
+++ b/src/components/Item/SpacerItem/Item.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import i18n from '@dhis2/d2-i18n'
import { colors } from '@dhis2/ui'
-import ItemHeader from '../ItemHeader'
+import ItemHeader from '../ItemHeader/ItemHeader'
const style = {
margin: '21px 28px',
@@ -15,7 +15,11 @@ const style = {
const SpacerItem = props => {
return (
<>
-
+
{i18n.t(
'Use a spacer to create empty vertical space between other dashboard items.'
@@ -26,6 +30,7 @@ const SpacerItem = props => {
}
SpacerItem.propTypes = {
+ dashboardMode: PropTypes.string,
item: PropTypes.object,
}
diff --git a/src/components/Item/TextItem/Item.js b/src/components/Item/TextItem/Item.js
index 8f561fcc5..af1e6a917 100644
--- a/src/components/Item/TextItem/Item.js
+++ b/src/components/Item/TextItem/Item.js
@@ -4,6 +4,8 @@ import i18n from '@dhis2/d2-i18n'
import PropTypes from 'prop-types'
import Input from '@material-ui/core/Input'
+import ItemHeader from '../ItemHeader/ItemHeader'
+import Line from '../../../widgets/Line'
import {
Parser as RichTextParser,
Editor as RichTextEditor,
@@ -12,8 +14,11 @@ import {
import { acUpdateDashboardItem } from '../../../actions/editDashboard'
import { sGetEditDashboardItems } from '../../../reducers/editDashboard'
import { sGetDashboardItems } from '../../../reducers/dashboards'
-import ItemHeader from '../ItemHeader'
-import Line from '../../../widgets/Line'
+import {
+ sGetIsPrinting,
+ sGetPrintDashboardItems,
+} from '../../../reducers/printDashboard'
+import { isEditMode } from '../../Dashboard/dashboardModes'
const style = {
textDiv: {
@@ -36,7 +41,7 @@ const style = {
}
const TextItem = props => {
- const { item, editMode, text, acUpdateDashboardItem } = props
+ const { item, dashboardMode, text, acUpdateDashboardItem } = props
const onChangeText = event => {
const updatedItem = {
@@ -59,7 +64,11 @@ const TextItem = props => {
const editItem = () => {
return (
<>
-
+
@@ -77,13 +86,19 @@ const TextItem = props => {
)
}
- return <>{editMode ? editItem() : viewItem()}>
+ return <>{isEditMode(dashboardMode) ? editItem() : viewItem()}>
}
const mapStateToProps = (state, ownProps) => {
- const items = ownProps.editMode
- ? sGetEditDashboardItems(state)
- : sGetDashboardItems(state)
+ const isPrintPreview = sGetIsPrinting(state)
+ let items
+ if (isPrintPreview) {
+ items = sGetPrintDashboardItems(state)
+ } else if (isEditMode(ownProps.dashboardMode)) {
+ items = sGetEditDashboardItems(state)
+ } else {
+ items = sGetDashboardItems(state)
+ }
const item = items.find(item => item.id === ownProps.item.id)
@@ -94,7 +109,7 @@ const mapStateToProps = (state, ownProps) => {
TextItem.propTypes = {
acUpdateDashboardItem: PropTypes.func,
- editMode: PropTypes.bool,
+ dashboardMode: PropTypes.string,
item: PropTypes.object,
text: PropTypes.string,
}
diff --git a/src/components/Item/VisualizationItem/DefaultPlugin.js b/src/components/Item/VisualizationItem/DefaultPlugin.js
index 512801705..8415526e8 100644
--- a/src/components/Item/VisualizationItem/DefaultPlugin.js
+++ b/src/components/Item/VisualizationItem/DefaultPlugin.js
@@ -83,7 +83,9 @@ class DefaultPlugin extends Component {
) {
pluginManager.load(this.props.item, this.props.visualization, {
credentials: this.pluginCredentials,
- activeType: !this.props.editMode ? this.getActiveType() : null,
+ activeType: this.props.useActiveType
+ ? this.getActiveType()
+ : null,
options: this.props.options,
})
}
@@ -119,7 +121,7 @@ DefaultPlugin.contextTypes = {
DefaultPlugin.propTypes = {
classes: PropTypes.object,
- editMode: PropTypes.bool,
+ useActiveType: PropTypes.bool,
item: PropTypes.object,
itemFilters: PropTypes.object,
options: PropTypes.object,
diff --git a/src/components/Item/VisualizationItem/Item.js b/src/components/Item/VisualizationItem/Item.js
index 9f9e00df9..f2f09a08a 100644
--- a/src/components/Item/VisualizationItem/Item.js
+++ b/src/components/Item/VisualizationItem/Item.js
@@ -7,12 +7,17 @@ import i18n from '@dhis2/d2-i18n'
import DefaultPlugin from './DefaultPlugin'
import FatalErrorBoundary from './FatalErrorBoundary'
-import ItemHeader, { HEADER_MARGIN_HEIGHT } from '../ItemHeader'
+import ItemHeader, { HEADER_MARGIN_HEIGHT } from '../ItemHeader/ItemHeader'
import ItemHeaderButtons from './ItemHeaderButtons'
import ItemFooter from './ItemFooter'
+import LoadingMask from './LoadingMask'
+
import * as pluginManager from './plugin'
import { sGetVisualization } from '../../../reducers/visualizations'
-import { sGetItemFiltersRoot } from '../../../reducers/itemFilters'
+import {
+ sGetItemFiltersRoot,
+ DEFAULT_STATE_ITEM_FILTERS,
+} from '../../../reducers/itemFilters'
import {
acAddVisualization,
acSetActiveVisualizationType,
@@ -24,9 +29,12 @@ import {
REPORT_TABLE,
} from '../../../modules/itemTypes'
import memoizeOne from '../../../modules/memoizeOne'
+import {
+ isEditMode,
+ isPrintMode,
+ isViewMode,
+} from '../../Dashboard/dashboardModes'
-import { getVisualizationConfig } from './plugin'
-import LoadingMask from './LoadingMask'
import { ITEM_CONTENT_PADDING_BOTTOM } from '../../ItemGrid/ItemGrid'
import classes from './styles/Item.module.css'
@@ -48,7 +56,9 @@ export class Item extends Component {
this.memoizedApplyFilters = memoizeOne(this.applyFilters)
- this.memoizedGetVisualizationConfig = memoizeOne(getVisualizationConfig)
+ this.memoizedGetVisualizationConfig = memoizeOne(
+ pluginManager.getVisualizationConfig
+ )
this.memoizedGetContentStyle = memoizeOne(this.getContentStyle)
}
@@ -145,12 +155,14 @@ export class Item extends Component {
const props = {
...this.props,
+ useActiveType: !isEditMode(this.props.dashboardMode),
visualization,
classes,
style: this.memoizedGetContentStyle(
calculatedHeight,
this.contentRef ? this.contentRef.offsetHeight : null,
- this.props.editMode
+ isEditMode(this.props.dashboardMode) ||
+ isPrintMode(this.props.dashboardMode)
),
}
@@ -260,8 +272,8 @@ export class Item extends Component {
this.props.visualization
)
- getContentStyle = (calculatedHeight, measuredHeight, editMode) => {
- const height = editMode
+ getContentStyle = (calculatedHeight, measuredHeight, preferMeasured) => {
+ const height = preferMeasured
? measuredHeight || calculatedHeight
: calculatedHeight
@@ -269,7 +281,7 @@ export class Item extends Component {
}
render() {
- const { item, editMode, itemFilters } = this.props
+ const { item, dashboardMode, itemFilters } = this.props
const { showFooter } = this.state
const actionButtons = (
@@ -291,6 +303,7 @@ export class Item extends Component {
itemId={item.id}
actionButtons={actionButtons}
ref={this.headerRef}
+ dashboardMode={dashboardMode}
/>
- {!editMode && showFooter ?
: null}
+ {isViewMode(dashboardMode) && showFooter ? (
+
+ ) : null}
>
)
}
@@ -312,7 +327,7 @@ Item.contextTypes = {
}
Item.propTypes = {
- editMode: PropTypes.bool,
+ dashboardMode: PropTypes.string,
item: PropTypes.object,
itemFilters: PropTypes.object,
visualization: PropTypes.object,
@@ -323,19 +338,23 @@ Item.propTypes = {
Item.defaultProps = {
item: {},
- editMode: false,
onToggleItemExpanded: Function.prototype,
- itemFilters: {},
visualization: {},
}
-const mapStateToProps = (state, ownProps) => ({
- itemFilters: sGetItemFiltersRoot(state),
- visualization: sGetVisualization(
- state,
- pluginManager.extractFavorite(ownProps.item).id
- ),
-})
+const mapStateToProps = (state, ownProps) => {
+ const itemFilters = !isEditMode(ownProps.dashboardMode)
+ ? sGetItemFiltersRoot(state)
+ : DEFAULT_STATE_ITEM_FILTERS
+
+ return {
+ itemFilters,
+ visualization: sGetVisualization(
+ state,
+ pluginManager.extractFavorite(ownProps.item).id
+ ),
+ }
+}
const mapDispatchToProps = dispatch => ({
onVisualizationLoaded: visualization =>
diff --git a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap
index 5846ccaaf..8e58a65c7 100644
--- a/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap
+++ b/src/components/Item/VisualizationItem/__tests__/__snapshots__/Item.spec.js.snap
@@ -2,7 +2,7 @@
exports[`VisualizationItem/Item renders a DefaultPlugin when an EVENT_CHART is passed 1`] = `
- {
const [showPopover, setShowPopover] = useState(false)
+ const [filters, setFilters] = useState(props.initiallySelectedItems)
+
const ref = useRef(null)
const closePanel = () => setShowPopover(false)
@@ -43,42 +43,31 @@ const FilterSelector = props => {
}
const onSelectItems = ({ dimensionId, items }) => {
- props.setEditItemFilters({
- ...props.selectedItems,
- [dimensionId]: items,
- })
+ setFilters({ ...filters, [dimensionId]: items })
}
const onDeselectItems = ({ dimensionId, itemIdsToRemove }) => {
- const oldList = props.selectedItems[dimensionId] || []
+ const oldList = filters[dimensionId] || []
const newList = oldList.filter(
item => !itemIdsToRemove.includes(item.id)
)
- if (newList.length) {
- props.setEditItemFilters({
- ...props.selectedItems,
- [dimensionId]: newList,
- })
- } else {
- props.removeEditItemFilter(dimensionId)
- }
+ const list = newList.length ? newList : []
+
+ setFilters({ ...filters, [dimensionId]: list })
}
const onReorderItems = ({ dimensionId, itemIds }) => {
- const oldList = props.selectedItems[dimensionId] || []
+ const oldList = filters[dimensionId] || []
const reorderedList = itemIds.map(id =>
oldList.find(item => item.id === id)
)
- props.setEditItemFilters({
- ...props.selectedItems,
- [dimensionId]: reorderedList,
- })
+ setFilters({ ...filters, [dimensionId]: reorderedList })
}
const saveFilter = id => {
- const filterItems = props.selectedItems[id]
+ const filterItems = filters[id]
if (filterItems && filterItems.length) {
props.addItemFilter({
@@ -119,9 +108,7 @@ const FilterSelector = props => {
({
displayNameProperty: sGetSettingsDisplayNameProperty(state),
dimension: sGetActiveModalDimension(state),
dimensions: sGetDimensions(state),
+ initiallySelectedItems: sGetItemFiltersRoot(state),
selectedDimensions: sGetFiltersKeys(state),
- selectedItems: sGetEditItemFiltersRoot(state),
})
FilterSelector.propTypes = {
@@ -147,12 +134,10 @@ FilterSelector.propTypes = {
dimension: PropTypes.object,
dimensions: PropTypes.array,
displayNameProperty: PropTypes.string,
- removeEditItemFilter: PropTypes.func,
+ initiallySelectedItems: PropTypes.object,
removeItemFilter: PropTypes.func,
selectedDimensions: PropTypes.array,
- selectedItems: PropTypes.object,
setActiveModalDimension: PropTypes.func,
- setEditItemFilters: PropTypes.func,
}
export default connect(mapStateToProps, {
@@ -160,6 +145,4 @@ export default connect(mapStateToProps, {
setActiveModalDimension: acSetActiveModalDimension,
addItemFilter: acAddItemFilter,
removeItemFilter: acRemoveItemFilter,
- removeEditItemFilter: acRemoveEditItemFilter,
- setEditItemFilters: acSetEditItemFilters,
})(FilterSelector)
diff --git a/src/components/ItemGrid/ItemGrid.js b/src/components/ItemGrid/ItemGrid.js
index 1dcf1c00c..87f87bff3 100644
--- a/src/components/ItemGrid/ItemGrid.js
+++ b/src/components/ItemGrid/ItemGrid.js
@@ -5,10 +5,7 @@ import i18n from '@dhis2/d2-i18n'
import ReactGridLayout from 'react-grid-layout'
import { Layer, CenteredContent, CircularLoader } from '@dhis2/ui'
-import {
- acUpdateDashboardLayout,
- acRemoveDashboardItem,
-} from '../../actions/editDashboard'
+import { acUpdateDashboardLayout } from '../../actions/editDashboard'
import { Item } from '../Item/Item'
import { resize as pluginResize } from '../Item/VisualizationItem/plugin'
import { isVisualizationType } from '../../modules/itemTypes'
@@ -38,6 +35,7 @@ import {
sGetDashboardItems,
} from '../../reducers/dashboards'
import ProgressiveLoadingContainer from '../Item/ProgressiveLoadingContainer'
+import { VIEW, EDIT } from '../Dashboard/dashboardModes'
// Component
@@ -61,10 +59,6 @@ export class ItemGrid extends Component {
this.setState({ expandedItems })
}
- onRemoveItem = clickedId => {
- this.props.acRemoveDashboardItem(clickedId)
- }
-
componentWillReceiveProps(nextProps) {
if (nextProps.edit) {
this.setState({ expandedItems: {} })
@@ -90,8 +84,6 @@ export class ItemGrid extends Component {
}
}
- onRemoveItemWrapper = id => () => this.onRemoveItem(id)
-
adjustHeightForExpanded = dashboardItem => {
const expandedItem = this.state.expandedItems[dashboardItem.id]
@@ -117,7 +109,7 @@ export class ItemGrid extends Component {
>
@@ -172,7 +164,6 @@ export class ItemGrid extends Component {
}
ItemGrid.propTypes = {
- acRemoveDashboardItem: PropTypes.func,
acUpdateDashboardLayout: PropTypes.func,
dashboardItems: PropTypes.array,
edit: PropTypes.bool,
@@ -202,7 +193,6 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = {
acUpdateDashboardLayout,
- acRemoveDashboardItem,
}
const mergeProps = (stateProps, dispatchProps, ownProps) => {
diff --git a/src/components/ItemGrid/PrintItemGrid.js b/src/components/ItemGrid/PrintItemGrid.js
new file mode 100644
index 000000000..6a9578ac0
--- /dev/null
+++ b/src/components/ItemGrid/PrintItemGrid.js
@@ -0,0 +1,119 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
+import ReactGridLayout from 'react-grid-layout'
+import { Layer, CenteredContent, CircularLoader } from '@dhis2/ui'
+
+import { Item } from '../Item/Item'
+import NoContentMessage from '../../widgets/NoContentMessage'
+
+import { PRINT } from '../Dashboard/dashboardModes'
+import { sGetSelectedIsLoading } from '../../reducers/selected'
+import {
+ sGetPrintDashboardRoot,
+ sGetPrintDashboardItems,
+} from '../../reducers/printDashboard'
+
+import {
+ GRID_ROW_HEIGHT,
+ GRID_COMPACT_TYPE,
+ MARGIN,
+ getGridColumns,
+ hasShape,
+} from './gridUtil'
+import { a4LandscapeWidthPx } from '../../modules/printUtils'
+import { orArray } from '../../modules/util'
+import { PAGEBREAK } from '../../modules/itemTypes'
+
+import 'react-grid-layout/css/styles.css'
+
+import './ItemGrid.css'
+
+export class PrintItemGrid extends Component {
+ getItemComponent = item => {
+ const itemClassNames = [item.type, 'print', 'oipp'].join(' ')
+
+ // TODO: this mutates the redux store
+ item.w = 56
+
+ if (item.type === PAGEBREAK) {
+ item.h = 3
+ } else {
+ item.h = 35
+ }
+
+ return (
+
+
+
+ )
+ }
+
+ getItemComponents = items => items.map(item => this.getItemComponent(item))
+
+ render() {
+ const { isLoading, dashboardItems } = this.props
+
+ if (!isLoading && !dashboardItems.length) {
+ return (
+
+ )
+ }
+
+ const width =
+ a4LandscapeWidthPx < window.innerWidth
+ ? a4LandscapeWidthPx
+ : window.innerWidth
+
+ return (
+ <>
+ {isLoading ? (
+
+
+
+
+
+ ) : null}
+
+ {this.getItemComponents(dashboardItems)}
+
+ >
+ )
+ }
+}
+
+PrintItemGrid.propTypes = {
+ dashboardItems: PropTypes.array,
+ isLoading: PropTypes.bool,
+}
+
+PrintItemGrid.defaultProps = {
+ dashboardItems: [],
+}
+
+const mapStateToProps = state => {
+ const selectedDashboard = sGetPrintDashboardRoot(state)
+
+ return {
+ isLoading: sGetSelectedIsLoading(state) || !selectedDashboard,
+ dashboardItems: orArray(sGetPrintDashboardItems(state)).filter(
+ hasShape
+ ),
+ }
+}
+
+export default connect(mapStateToProps)(PrintItemGrid)
diff --git a/src/components/ItemGrid/PrintLayoutItemGrid.js b/src/components/ItemGrid/PrintLayoutItemGrid.js
new file mode 100644
index 000000000..58a20cfbc
--- /dev/null
+++ b/src/components/ItemGrid/PrintLayoutItemGrid.js
@@ -0,0 +1,203 @@
+import React, { Component } from 'react'
+import { connect } from 'react-redux'
+import PropTypes from 'prop-types'
+import i18n from '@dhis2/d2-i18n'
+import ReactGridLayout from 'react-grid-layout'
+import { Layer, CenteredContent, CircularLoader } from '@dhis2/ui'
+import sortBy from 'lodash/sortBy'
+
+import { Item } from '../Item/Item'
+
+import { acUpdatePrintDashboardLayout } from '../../actions/printDashboard'
+import {
+ GRID_ROW_HEIGHT,
+ GRID_COMPACT_TYPE,
+ MARGIN,
+ getGridColumns,
+ hasShape,
+} from './gridUtil'
+import { orArray } from '../../modules/util'
+import { a4LandscapeWidthPx } from '../../modules/printUtils'
+import { PRINT_LAYOUT } from '../Dashboard/dashboardModes'
+import { itemTypeMap, PAGEBREAK } from '../../modules/itemTypes'
+
+import NoContentMessage from '../../widgets/NoContentMessage'
+
+import 'react-grid-layout/css/styles.css'
+import 'react-resizable/css/styles.css'
+
+import './ItemGrid.css'
+import { sGetSelectedIsLoading } from '../../reducers/selected'
+import {
+ sGetPrintDashboardRoot,
+ sGetPrintDashboardItems,
+} from '../../reducers/printDashboard'
+
+// this is set in the .dashboard-item-content css
+export const ITEM_CONTENT_PADDING_BOTTOM = 4
+
+export class PrintLayoutItemGrid extends Component {
+ onLayoutChange = newLayout => {
+ this.props.updateDashboardLayout(newLayout)
+ }
+ getItemComponent = item => {
+ const itemClassNames = [item.type, 'print', 'layout'].join(' ')
+
+ return (
+
+
+
+ )
+ }
+
+ getItemComponents = items => items.map(item => this.getItemComponent(item))
+
+ hideExtraPageBreaks() {
+ const elements = Array.from(
+ document.querySelectorAll('.react-grid-item')
+ )
+
+ const types = Object.keys(itemTypeMap)
+ const items = elements.map(el => {
+ const classNames = el.className.split(' ')
+
+ const type = classNames.find(
+ className => types.indexOf(className) > -1
+ )
+
+ const rect = el.getBoundingClientRect()
+
+ return {
+ type,
+ el,
+ y: rect.y,
+ h: rect.height,
+ }
+ })
+ const sortedItems = sortBy(items, ['y'])
+
+ let pageBreakBottom = 0
+ let lastItemBottom = 0
+ let foundNonPageBreak = false
+
+ for (let i = sortedItems.length - 1; i >= 0; --i) {
+ const item = sortedItems[i]
+ if (item.type === PAGEBREAK) {
+ if (!foundNonPageBreak) {
+ item.el.classList.add('removed')
+ } else {
+ const y = item.el.style.transform
+ ? item.el.style.transform.split(' ')[1].slice(0, -3)
+ : item.y
+
+ pageBreakBottom = parseInt(y) + parseInt(item.h)
+ break
+ }
+ } else {
+ foundNonPageBreak = true
+ const y = item.el.style.transform
+ ? item.el.style.transform.split(' ')[1].slice(0, -3)
+ : item.y
+
+ const thisItemBottom = parseInt(y) + parseInt(item.h)
+ if (thisItemBottom > lastItemBottom) {
+ lastItemBottom = thisItemBottom
+ }
+ }
+ }
+
+ const pageHeight = 720
+ const gridElement = document.querySelector('.react-grid-layout')
+ const maxHeight = pageBreakBottom + pageHeight
+
+ if (maxHeight < lastItemBottom) {
+ // there is a problem - this should not happen
+ console.log(
+ 'jj PROBLEM! items extend beyond page bottom',
+ maxHeight,
+ '<',
+ lastItemBottom
+ )
+ }
+ if (gridElement) {
+ gridElement.style.height = `${maxHeight}px`
+ }
+ }
+
+ componentDidMount() {
+ this.hideExtraPageBreaks()
+ }
+
+ componentDidUpdate() {
+ this.hideExtraPageBreaks()
+ }
+
+ render() {
+ const { isLoading, dashboardItems } = this.props
+
+ if (!isLoading && !dashboardItems.length) {
+ return (
+
+ )
+ }
+
+ const width =
+ a4LandscapeWidthPx < window.innerWidth
+ ? a4LandscapeWidthPx
+ : window.innerWidth
+
+ return (
+
+ {isLoading ? (
+
+
+
+
+
+ ) : null}
+
+ {this.getItemComponents(dashboardItems)}
+
+
+ )
+ }
+}
+
+PrintLayoutItemGrid.propTypes = {
+ dashboardItems: PropTypes.array,
+ isLoading: PropTypes.bool,
+ updateDashboardLayout: PropTypes.func,
+}
+
+PrintLayoutItemGrid.defaultProps = {
+ dashboardItems: [],
+}
+
+const mapStateToProps = state => {
+ const selectedDashboard = sGetPrintDashboardRoot(state)
+
+ return {
+ isLoading: sGetSelectedIsLoading(state) || !selectedDashboard,
+ dashboardItems: orArray(sGetPrintDashboardItems(state)).filter(
+ hasShape
+ ),
+ }
+}
+
+export default connect(mapStateToProps, {
+ updateDashboardLayout: acUpdatePrintDashboardLayout,
+})(PrintLayoutItemGrid)
diff --git a/src/components/ItemGrid/gridUtil.js b/src/components/ItemGrid/gridUtil.js
index e73efde08..060c4b6b6 100644
--- a/src/components/ItemGrid/gridUtil.js
+++ b/src/components/ItemGrid/gridUtil.js
@@ -67,6 +67,28 @@ export const getGridItemProperties = itemId => {
}
}
+export const getPageBreakItemShape = (yPos, isStatic = true) => {
+ return {
+ x: 0,
+ y: yPos,
+ w: GRID_COLUMNS - 1,
+ h: 5,
+ static: !!isStatic,
+ minH: 1,
+ }
+}
+
+export const getPrintTitlePageItemShape = () => {
+ return {
+ x: 0,
+ y: 0,
+ w: GRID_COLUMNS - 1,
+ h: 30,
+ static: true,
+ minH: 1,
+ }
+}
+
/**
* Calculates the grid item's original height in pixels based
* on the height in grid units. This calculation
@@ -74,7 +96,7 @@ export const getGridItemProperties = itemId => {
*
* @param {Object} item item containing shape (x, y, w, h)
*/
-const getOriginalHeight = item => {
+export const getOriginalHeight = item => {
const originalHeight = Math.round(
GRID_ROW_HEIGHT * item.h + Math.max(0, item.h - 1) * MARGIN[1]
)
diff --git a/src/components/TitleBar/ViewTitleBar.js b/src/components/TitleBar/ViewTitleBar.js
index c84c5131d..cdbca529c 100644
--- a/src/components/TitleBar/ViewTitleBar.js
+++ b/src/components/TitleBar/ViewTitleBar.js
@@ -1,18 +1,19 @@
-import React, { Component } from 'react'
+import React, { useState, createRef } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Link } from 'react-router-dom'
import i18n from '@dhis2/d2-i18n'
+import { Redirect } from 'react-router-dom'
import SharingDialog from '@dhis2/d2-ui-sharing-dialog'
import Star from '@material-ui/icons/Star'
import StarBorder from '@material-ui/icons/StarBorder'
+import { Button, FlyoutMenu, Popover, MenuItem, colors } from '@dhis2/ui'
+import { ThreeDots } from '../Item/VisualizationItem/assets/icons'
import { orObject } from '../../modules/util'
import { tStarDashboard } from '../../actions/dashboards'
-import { acSetSelectedShowDescription } from '../../actions/selected'
+import { tUpdateShowDescription } from '../../actions/selected'
import FilterSelector from '../ItemFilter/FilterSelector'
-import { Button, colors } from '@dhis2/ui'
-import Info from './Info'
import {
sGetSelectedId,
sGetSelectedShowDescription,
@@ -24,101 +25,155 @@ import {
import classes from './styles/ViewTitleBar.module.css'
-const NO_DESCRIPTION = i18n.t('No description')
+const ViewTitleBar = (props, context) => {
+ const [moreOptionsIsOpen, setMoreOptionsIsOpen] = useState(false)
+ const [sharingDialogIsOpen, setSharingDialogIsOpen] = useState(false)
+ const [redirectUrl, setRedirectUrl] = useState(null)
-class ViewTitleBar extends Component {
- constructor(props) {
- super(props)
+ const {
+ id,
+ name,
+ description,
+ access,
+ style,
+ showDescription,
+ starred,
+ onToggleStarredDashboard,
+ onShowHideDescription,
+ } = props
+
+ const toggleSharingDialog = () =>
+ setSharingDialogIsOpen(!sharingDialogIsOpen)
+
+ const toggleMoreOptions = () => setMoreOptionsIsOpen(!moreOptionsIsOpen)
+
+ const printLayout = () => setRedirectUrl(`${id}/printlayout`)
+ const printOipp = () => setRedirectUrl(`${id}/printoipp`)
+
+ const titleStyle = Object.assign({}, style.title, {
+ cursor: 'default',
+ userSelect: 'text',
+ top: '7px',
+ })
+
+ const StarIcon = starred ? Star : StarBorder
+
+ if (redirectUrl) {
+ return
+ }
+
+ const showHideDescriptionLabel = showDescription
+ ? i18n.t('Hide description')
+ : i18n.t('Show description')
- this.state = {
- sharingDialogIsOpen: false,
- }
+ const showHideDescription = () => {
+ onShowHideDescription()
+ toggleMoreOptions()
}
- toggleSharingDialog = () =>
- this.setState({ sharingDialogIsOpen: !this.state.sharingDialogIsOpen })
-
- render() {
- const {
- id,
- name,
- description,
- access,
- style,
- showDescription,
- starred,
- onStarClick,
- onInfoClick,
- } = this.props
-
- const titleStyle = Object.assign({}, style.title, {
- cursor: 'default',
- userSelect: 'text',
- top: '7px',
- })
-
- const StarIcon = starred ? Star : StarBorder
-
- return (
- <>
-
-
{name}
-
-
-
-
-
-
-
-
- {access.update ? (
-
-
-
- ) : null}
- {access.manage ? (
-
-
-
- ) : null}
-
-
-
-
-
-
- {showDescription ? (
+ const toggleStarredDashboardLabel = starred
+ ? i18n.t('Unstar dashboard')
+ : i18n.t('Star dashboard')
+
+ const toggleStarredDashboard = () => {
+ onToggleStarredDashboard()
+ toggleMoreOptions()
+ }
+
+ const buttonRef = createRef()
+
+ return (
+ <>
+
+
{name}
+
- {description || NO_DESCRIPTION}
+
- ) : null}
- {id ? (
-
- ) : null}
- >
- )
- }
+
+ {access.update ? (
+
+
+
+ ) : null}
+ {access.manage ? (
+
+ ) : null}
+
+
+
+
+
+ {moreOptionsIsOpen && (
+
+
+
+
+
+
+
+ )}
+
+
+ {showDescription ? (
+
+ {description || i18n.t('No description')}
+
+ ) : null}
+ {id ? (
+
+ ) : null}
+ >
+ )
}
ViewTitleBar.propTypes = {
@@ -129,8 +184,8 @@ ViewTitleBar.propTypes = {
showDescription: PropTypes.bool,
starred: PropTypes.bool,
style: PropTypes.object,
- onInfoClick: PropTypes.func,
- onStarClick: PropTypes.func,
+ onShowHideDescription: PropTypes.func,
+ onToggleStarredDashboard: PropTypes.func,
}
ViewTitleBar.defaultProps = {
@@ -138,7 +193,6 @@ ViewTitleBar.defaultProps = {
description: '',
starred: false,
showDescription: false,
- onInfoClick: null,
}
ViewTitleBar.contextTypes = {
@@ -167,9 +221,9 @@ const mergeProps = (stateProps, dispatchProps, ownProps) => {
return {
...stateProps,
...ownProps,
- onStarClick: () => dispatch(tStarDashboard(id, !starred)),
- onInfoClick: () =>
- dispatch(acSetSelectedShowDescription(!showDescription)),
+ onToggleStarredDashboard: () => dispatch(tStarDashboard(id, !starred)),
+ onShowHideDescription: () =>
+ dispatch(tUpdateShowDescription(!showDescription)),
}
}
diff --git a/src/components/TitleBar/styles/PrintTitleBar.module.css b/src/components/TitleBar/styles/PrintTitleBar.module.css
new file mode 100644
index 000000000..7dfa63af8
--- /dev/null
+++ b/src/components/TitleBar/styles/PrintTitleBar.module.css
@@ -0,0 +1,31 @@
+.link {
+ display: inline-block;
+ vertical-align: top;
+ text-decoration: none;
+ margin-right: 4px;
+}
+.title {
+ font-size: 20px;
+ font-weight: 500;
+ color: var(--colors-grey700);
+ cursor: default;
+ user-select: text;
+ margin: 0px 0px 0px 10px;
+}
+.description {
+ margin: 15px 0px 0px 10px;
+ font-size: 14px;
+ color: var(--colors-grey700);
+}
+
+.filters {
+ margin: 10px;
+ font-size: 14px;
+ color: var(--colors-grey700);
+}
+
+@media print {
+ .link {
+ display: none;
+ }
+}
diff --git a/src/components/TitleBar/styles/ViewTitleBar.module.css b/src/components/TitleBar/styles/ViewTitleBar.module.css
index b796e5d03..221ac8735 100644
--- a/src/components/TitleBar/styles/ViewTitleBar.module.css
+++ b/src/components/TitleBar/styles/ViewTitleBar.module.css
@@ -4,10 +4,7 @@
margin-left: 20px;
}
.editLink {
- display: inline-block;
- vertical-align: top;
text-decoration: none;
- margin-right: 4px;
}
.titleBar {
display: flex;
@@ -19,3 +16,20 @@
top: 1px;
cursor: pointer;
}
+.strip {
+ display: flex;
+ margin-left: var(--spacers-dp8);
+}
+
+.strip > * {
+ margin-left: var(--spacers-dp8);
+}
+
+.backdrop {
+ position: fixed;
+ width: 100%;
+ height: 100%;
+ top: 0;
+ left: 0;
+ background-color: transparent;
+}
diff --git a/src/components/__tests__/App.spec.js b/src/components/__tests__/App.spec.js
index 71ecfff2b..1db456c46 100644
--- a/src/components/__tests__/App.spec.js
+++ b/src/components/__tests__/App.spec.js
@@ -2,12 +2,7 @@ import React from 'react'
import { shallow } from 'enzyme'
import { App } from '../App'
-/* eslint-disable react/display-name */
-jest.mock('../Dashboard/Dashboard', () => () => (
- mockDashboard
-))
-/* eslint-enable react/display-name */
-
+jest.mock('../Dashboard/Dashboard', () => 'Dashboard')
jest.mock('../../actions/dimensions', () => ({ tSetDimensions: () => null }))
jest.mock('../../actions/dashboards', () => ({ tFetchDashboards: () => null }))
diff --git a/src/icons/LessHorizontal.js b/src/icons/LessHorizontal.js
new file mode 100644
index 000000000..6da6f8bc5
--- /dev/null
+++ b/src/icons/LessHorizontal.js
@@ -0,0 +1,16 @@
+import React from 'react'
+import { colors } from '@dhis2/ui'
+
+const LessHorizontalIcon = () => (
+
+)
+
+export default LessHorizontalIcon
diff --git a/src/modules/itemTypes.js b/src/modules/itemTypes.js
index d3a772011..669ffbd15 100644
--- a/src/modules/itemTypes.js
+++ b/src/modules/itemTypes.js
@@ -26,6 +26,8 @@ export const USERS = 'USERS'
export const MESSAGES = 'MESSAGES'
export const TEXT = 'TEXT'
export const SPACER = 'SPACER'
+export const PAGEBREAK = 'PAGEBREAK'
+export const PRINT_TITLE_PAGE = 'PRINT_TITLE_PAGE'
const DOMAIN_TYPE_AGGREGATE = 'AGGREGATE'
const DOMAIN_TYPE_TRACKER = 'TRACKER'
@@ -148,6 +150,12 @@ export const itemTypeMap = {
[SPACER]: {
propName: 'text',
},
+ [PAGEBREAK]: {
+ propName: 'text',
+ },
+ [PRINT_TITLE_PAGE]: {
+ propName: 'text',
+ },
}
export const getEndPointName = type => itemTypeMap[type].endPointName
diff --git a/src/modules/printUtils.js b/src/modules/printUtils.js
new file mode 100644
index 000000000..2bc138ac2
--- /dev/null
+++ b/src/modules/printUtils.js
@@ -0,0 +1,5 @@
+// for A4 landscape (297x210mm)
+// 794 px = (21cm / 2.54) * 96 pixels/inch
+// 1122 px = 29.7 /2.54 * 96 pixels/inch
+// const a4LandscapeHeightPx = 794
+export const a4LandscapeWidthPx = 1102
diff --git a/src/reducers/__tests__/editItemFilters.js b/src/reducers/__tests__/editItemFilters.js
deleted file mode 100644
index 3e36996f0..000000000
--- a/src/reducers/__tests__/editItemFilters.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import reducer, {
- DEFAULT_STATE_EDIT_ITEM_FILTERS,
- SET_EDIT_ITEM_FILTERS,
- REMOVE_EDIT_ITEM_FILTER,
- CLEAR_EDIT_ITEM_FILTERS,
-} from '../editItemFilters'
-
-const testKey = 'ou'
-const testValue = [{ id: 'ou1', name: 'OU test' }]
-
-const testState = {
- [testKey]: testValue,
-}
-
-describe('item filter reducer', () => {
- describe('reducer', () => {
- it('should return the default state', () => {
- const actualState = reducer(DEFAULT_STATE_EDIT_ITEM_FILTERS, {})
-
- expect(actualState).toEqual(DEFAULT_STATE_EDIT_ITEM_FILTERS)
- })
-
- it('should set a filter', () => {
- const action = {
- type: SET_EDIT_ITEM_FILTERS,
- filters: testState,
- }
-
- const expectedState = testState
-
- const actualState = reducer(undefined, action)
-
- expect(actualState).toEqual(expectedState)
- })
-
- it('should remove a filter', () => {
- const action = {
- type: REMOVE_EDIT_ITEM_FILTER,
- id: testKey,
- }
-
- const expectedState = DEFAULT_STATE_EDIT_ITEM_FILTERS
-
- const actualState = reducer(testState, action)
-
- expect(actualState).toEqual(expectedState)
- })
-
- it('should clear all filters', () => {
- const action = {
- type: CLEAR_EDIT_ITEM_FILTERS,
- }
-
- const expectedState = DEFAULT_STATE_EDIT_ITEM_FILTERS
-
- const actualState = reducer(testState, action)
-
- expect(actualState).toEqual(expectedState)
- })
- })
-})
diff --git a/src/reducers/editDashboard.js b/src/reducers/editDashboard.js
index 206843890..85aa26d7b 100644
--- a/src/reducers/editDashboard.js
+++ b/src/reducers/editDashboard.js
@@ -12,6 +12,8 @@ export const ADD_DASHBOARD_ITEM = 'ADD_DASHBOARD_ITEM'
export const REMOVE_DASHBOARD_ITEM = 'REMOVE_DASHBOARD_ITEM'
export const UPDATE_DASHBOARD_ITEM = 'UPDATE_DASHBOARD_ITEM'
export const RECEIVED_DASHBOARD_LAYOUT = 'RECEIVED_DASHBOARD_LAYOUT'
+export const SET_PRINT_PREVIEW_VIEW = 'SET_PRINT_PREVIEW_VIEW'
+export const CLEAR_PRINT_PREVIEW_VIEW = 'CLEAR_PRINT_PREVIEW_VIEW'
export const DEFAULT_STATE_EDIT_DASHBOARD = {}
export const NEW_DASHBOARD_STATE = {
@@ -20,6 +22,7 @@ export const NEW_DASHBOARD_STATE = {
access: {},
description: '',
dashboardItems: [],
+ printPreviewView: false,
}
export default (state = DEFAULT_STATE_EDIT_DASHBOARD, action) => {
@@ -29,10 +32,15 @@ export default (state = DEFAULT_STATE_EDIT_DASHBOARD, action) => {
Object.keys(NEW_DASHBOARD_STATE).map(
k => (newState[k] = action.value[k])
)
+ newState.printPreviewView = false
return newState
}
case RECEIVED_NOT_EDITING:
return DEFAULT_STATE_EDIT_DASHBOARD
+ case SET_PRINT_PREVIEW_VIEW:
+ return Object.assign({}, state, { printPreviewView: true })
+ case CLEAR_PRINT_PREVIEW_VIEW:
+ return Object.assign({}, state, { printPreviewView: false })
case START_NEW_DASHBOARD:
return NEW_DASHBOARD_STATE
case RECEIVED_TITLE: {
@@ -44,9 +52,20 @@ export default (state = DEFAULT_STATE_EDIT_DASHBOARD, action) => {
})
}
case ADD_DASHBOARD_ITEM:
+ if (!action.value.position) {
+ return update(state, {
+ dashboardItems: { $unshift: [action.value] },
+ })
+ }
+
return update(state, {
- dashboardItems: { $unshift: [action.value] },
+ dashboardItems: {
+ $splice: [
+ [parseInt(action.value.position), 0, action.value],
+ ],
+ },
})
+
case REMOVE_DASHBOARD_ITEM: {
const idToRemove = action.value
@@ -131,11 +150,18 @@ export const sGetEditDashboardRoot = state => state.editDashboard
export const sGetIsEditing = state => !isEmpty(state.editDashboard)
+export const sGetIsPrintPreviewView = state =>
+ sGetEditDashboardRoot(state).printPreviewView
+
export const sGetIsNewDashboard = state => {
return (
!isEmpty(state.editDashboard) && sGetEditDashboardRoot(state).id === ''
)
}
+export const sGetEditDashboardName = state => sGetEditDashboardRoot(state).name
+export const sGetEditDashboardDescription = state =>
+ sGetEditDashboardRoot(state).description
+
export const sGetEditDashboardItems = state =>
orObject(sGetEditDashboardRoot(state)).dashboardItems
diff --git a/src/reducers/editItemFilters.js b/src/reducers/editItemFilters.js
deleted file mode 100644
index 2defec5a1..000000000
--- a/src/reducers/editItemFilters.js
+++ /dev/null
@@ -1,32 +0,0 @@
-export const CLEAR_EDIT_ITEM_FILTERS = 'CLEAR_EDIT_ITEM_FILTERS'
-export const REMOVE_EDIT_ITEM_FILTER = 'REMOVE_EDIT_ITEM_FILTER'
-export const SET_EDIT_ITEM_FILTERS = 'SET_EDIT_ITEM_FILTERS'
-
-export const DEFAULT_STATE_EDIT_ITEM_FILTERS = {}
-
-export default (state = DEFAULT_STATE_EDIT_ITEM_FILTERS, action) => {
- switch (action.type) {
- case REMOVE_EDIT_ITEM_FILTER: {
- const newState = { ...state }
-
- delete newState[action.id]
-
- return newState
- }
- case SET_EDIT_ITEM_FILTERS: {
- return action.filters
- }
- case CLEAR_EDIT_ITEM_FILTERS: {
- return DEFAULT_STATE_EDIT_ITEM_FILTERS
- }
- default:
- return state
- }
-}
-
-// selectors
-
-export const sGetEditItemFiltersRoot = state => state.editItemFilters
-
-export const sGetEditFiltersKeys = state =>
- Object.keys(sGetEditItemFiltersRoot(state))
diff --git a/src/reducers/index.js b/src/reducers/index.js
index c61b6629f..e345db74c 100644
--- a/src/reducers/index.js
+++ b/src/reducers/index.js
@@ -12,11 +12,11 @@ import dashboardsFilter, {
import controlBar from './controlBar'
import visualizations from './visualizations'
import editDashboard from './editDashboard'
+import printDashboard from './printDashboard'
import messages from './messages'
import user from './user'
import snackbar from './snackbar'
import itemFilters from './itemFilters'
-import editItemFilters from './editItemFilters'
import style from './style'
import dimensions from './dimensions'
import settings from './settings'
@@ -35,8 +35,8 @@ export default combineReducers({
messages,
user,
editDashboard,
+ printDashboard,
itemFilters,
- editItemFilters,
style,
snackbar,
dimensions,
diff --git a/src/reducers/itemFilters.js b/src/reducers/itemFilters.js
index ab850beeb..a26433eeb 100644
--- a/src/reducers/itemFilters.js
+++ b/src/reducers/itemFilters.js
@@ -1,3 +1,6 @@
+import { sGetDimensions } from './dimensions'
+import { createSelector } from 'reselect'
+
export const CLEAR_ITEM_FILTERS = 'CLEAR_ITEM_FILTERS'
export const SET_ITEM_FILTERS = 'SET_ITEM_FILTERS'
export const ADD_ITEM_FILTER = 'ADD_ITEM_FILTER'
@@ -36,3 +39,22 @@ export default (state = DEFAULT_STATE_ITEM_FILTERS, action) => {
export const sGetItemFiltersRoot = state => state.itemFilters
export const sGetFiltersKeys = state => Object.keys(sGetItemFiltersRoot(state))
+
+// simplify the filters structure to:
+// [{ id: 'pe', name: 'Period', values: [{ id: 2019: name: '2019' }, {id: 'LAST_MONTH', name: 'Last month' }]}, ...]
+export const sGetNamedItemFilters = createSelector(
+ [sGetItemFiltersRoot, sGetDimensions],
+ (filters, dimensions) =>
+ Object.keys(filters).reduce((arr, id) => {
+ arr.push({
+ id: id,
+ name: dimensions.find(dimension => dimension.id === id).name,
+ values: filters[id].map(value => ({
+ id: value.id,
+ name: value.displayName || value.name,
+ })),
+ })
+
+ return arr
+ }, [])
+)
diff --git a/src/reducers/printDashboard.js b/src/reducers/printDashboard.js
new file mode 100644
index 000000000..56db6c237
--- /dev/null
+++ b/src/reducers/printDashboard.js
@@ -0,0 +1,133 @@
+/** @module reducers/editDashboard */
+import update from 'immutability-helper'
+import isEmpty from 'lodash/isEmpty'
+import { orArray, orObject } from '../modules/util'
+
+export const SET_PRINT_DASHBOARD = 'SET_PRINT_DASHBOARD'
+export const CLEAR_PRINT_DASHBOARD = 'CLEAR_PRINT_DASHBOARD'
+export const ADD_PRINT_DASHBOARD_ITEM = 'ADD_PRINT_DASHBOARD_ITEM'
+export const SET_PRINT_DASHBOARD_LAYOUT = 'SET_PRINT_DASHBOARD_LAYOUT'
+export const REMOVE_PRINT_DASHBOARD_ITEM = 'REMOVE_PRINT_DASHBOARD_ITEM'
+export const UPDATE_PRINT_DASHBOARD_ITEM = 'UPDATE_PRINT_DASHBOARD_ITEM'
+
+export const DEFAULT_STATE_PRINT_DASHBOARD = {}
+export const NEW_PRINT_DASHBOARD_STATE = {
+ id: '',
+ name: '',
+ access: {},
+ description: '',
+ dashboardItems: [],
+}
+
+export default (state = DEFAULT_STATE_PRINT_DASHBOARD, action) => {
+ switch (action.type) {
+ case SET_PRINT_DASHBOARD: {
+ const newState = {}
+ Object.keys(NEW_PRINT_DASHBOARD_STATE).map(
+ k => (newState[k] = action.value[k])
+ )
+ return newState
+ }
+ case CLEAR_PRINT_DASHBOARD:
+ return DEFAULT_STATE_PRINT_DASHBOARD
+ case ADD_PRINT_DASHBOARD_ITEM:
+ if (!action.value.position) {
+ return update(state, {
+ dashboardItems: { $unshift: [action.value] },
+ })
+ }
+
+ return update(state, {
+ dashboardItems: {
+ $splice: [
+ [parseInt(action.value.position), 0, action.value],
+ ],
+ },
+ })
+
+ case REMOVE_PRINT_DASHBOARD_ITEM: {
+ const idToRemove = action.value
+
+ const dashboardItemIndex = state.dashboardItems.findIndex(
+ item => item.id === idToRemove
+ )
+
+ if (dashboardItemIndex > -1) {
+ return update(state, {
+ dashboardItems: {
+ $splice: [[dashboardItemIndex, 1]],
+ },
+ })
+ }
+
+ return state
+ }
+ case UPDATE_PRINT_DASHBOARD_ITEM: {
+ const dashboardItem = action.value
+
+ const dashboardItemIndex = state.dashboardItems.findIndex(
+ item => item.id === dashboardItem.id
+ )
+
+ if (dashboardItemIndex > -1) {
+ const newState = update(state, {
+ dashboardItems: {
+ $splice: [
+ [
+ dashboardItemIndex,
+ 1,
+ Object.assign({}, dashboardItem),
+ ],
+ ],
+ },
+ })
+
+ return newState
+ }
+
+ return state
+ }
+ case SET_PRINT_DASHBOARD_LAYOUT: {
+ const stateItems = orArray(state.dashboardItems)
+ let layoutHasChanged = false
+
+ const newStateItems = action.value.map(({ x, y, w, h, i }) => {
+ const stateItem = stateItems.find(si => si.id === i)
+
+ if (
+ !(
+ stateItem.x === x &&
+ stateItem.y === y &&
+ stateItem.w === w &&
+ stateItem.h === h
+ )
+ ) {
+ layoutHasChanged = true
+ return Object.assign({}, stateItem, { w, h, x, y })
+ }
+
+ return stateItem
+ })
+
+ return layoutHasChanged
+ ? {
+ ...state,
+ dashboardItems: newStateItems,
+ }
+ : state
+ }
+ default:
+ return state
+ }
+}
+
+// root selector
+
+export const sGetPrintDashboardRoot = state => state.printDashboard
+
+// selectors
+
+export const sGetIsPrinting = state => !isEmpty(state.printDashboard)
+
+export const sGetPrintDashboardItems = state =>
+ orObject(sGetPrintDashboardRoot(state)).dashboardItems
diff --git a/src/reducers/selected.js b/src/reducers/selected.js
index f48e15142..32c304e46 100644
--- a/src/reducers/selected.js
+++ b/src/reducers/selected.js
@@ -11,6 +11,8 @@ export const DEFAULT_STATE_SELECTED_ID = null
export const DEFAULT_STATE_SELECTED_ISLOADING = false
export const DEFAULT_STATE_SELECTED_SHOWDESCRIPTION = false
+export const NON_EXISTING_DASHBOARD_ID = '0'
+
/**
* Reducer functions that computes and returns the new state based on the given action
* @function
diff --git a/yarn.lock b/yarn.lock
index 3f819c511..b6063c2ab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -11403,16 +11403,16 @@ react-final-form@^6.5.1:
dependencies:
"@babel/runtime" "^7.10.0"
-react-grid-layout@^0.18.3:
- version "0.18.3"
- resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-0.18.3.tgz#4f9540199f35211077eae0aa5bc83096a672c39c"
- integrity sha512-lHkrk941Tk5nTwZPa9uj6ttHBT0VehSHwEhWbINBJKvM1GRaFNOefvjcuxSyuCI5JWjVUP+Qm3ARt2470AlxMA==
+react-grid-layout@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/react-grid-layout/-/react-grid-layout-1.0.0.tgz#85495f1e9da2e5b9dad5ece82b3052d624be709d"
+ integrity sha512-0gSnLQL9dYZtAlZCjIPYIqZtswnzMhIT1uip78vs3J432FLOF6LD5PmYaRm34V/s6bEpS9cMZNMcsOanwZXBEw==
dependencies:
classnames "2.x"
lodash.isequal "^4.0.0"
prop-types "^15.0.0"
react-draggable "^4.0.0"
- react-resizable "^1.9.0"
+ react-resizable "^1.10.0"
react-input-autosize@^2.2.1:
version "2.2.2"
@@ -11470,7 +11470,7 @@ react-redux@^7.2.1:
prop-types "^15.7.2"
react-is "^16.9.0"
-react-resizable@^1.9.0:
+react-resizable@^1.10.0:
version "1.10.1"
resolved "https://registry.yarnpkg.com/react-resizable/-/react-resizable-1.10.1.tgz#f0c2cf1d83b3470b87676ce6d6b02bbe3f4d8cd4"
integrity sha512-Jd/bKOKx6+19NwC4/aMLRu/J9/krfxlDnElP41Oc+oLiUWs/zwV1S9yBfBZRnqAwQb6vQ/HRSk3bsSWGSgVbpw==