diff --git a/client/default.config.js b/client/default.config.js index 4a04c729..8442456b 100644 --- a/client/default.config.js +++ b/client/default.config.js @@ -33,7 +33,7 @@ module.exports = { APPROVED: 'Approved', }, appTypeToDisplayName: { - APP: 'Standard', + APP: 'Application', DASHBOARD_WIDGET: 'Dashboard', TRACKER_DASHBOARD_WIDGET: 'Tracker Dashboard', }, diff --git a/client/src/components/PluginTag/PluginTag.js b/client/src/components/PluginTag/PluginTag.js index d22d53af..f3b776fc 100644 --- a/client/src/components/PluginTag/PluginTag.js +++ b/client/src/components/PluginTag/PluginTag.js @@ -6,7 +6,7 @@ const PluginTag = ({ hasPlugin, pluginType }) => { } const tagString = pluginType ? `Plugin: ${pluginType}` : 'Plugin' - return {tagString} + return {tagString} } export default PluginTag diff --git a/client/src/components/Versions/ChangeLogViewer.js b/client/src/components/Versions/ChangeLogViewer.js index 59e482fb..180c17fe 100644 --- a/client/src/components/Versions/ChangeLogViewer.js +++ b/client/src/components/Versions/ChangeLogViewer.js @@ -1,45 +1,29 @@ import { CircularLoader, - DataTable, - DataTableCell, - DataTableColumnHeader, - DataTableHead, - DataTableRow, - Modal, - ModalContent, - ModalTitle, + Table, + TableCell, + TableRow, SingleSelectField, SingleSelectOption, } from '@dhis2/ui' -import { useQuery } from 'src/api' - -import React from 'react' -import Changelog from '../../utils/changelog' import classnames from 'classnames' +import PropTypes from 'prop-types' +import React, { useState } from 'react' +import Changelog from '../../utils/changelog' import styles from './ChangeLogViewer.module.css' +import { useQuery } from 'src/api' -const ChangeLogViewer = ({ - appId, - onCloseChangelog, - baseVersion, - setBaseVersion, - setCompareVersion, - compareVersion, -}) => { +const ChangeLogViewer = ({ appId, latestVersion }) => { const { data, loading } = useQuery(`apps/${appId}/changelog`) - if (!data || loading) { - return - } + const changelog = new Changelog(data?.changelog) - if (!data.changelog) { - return null - } + const allVersions = changelog?.data?.map(({ version }) => version) + const changesCount = allVersions?.length - const changelog = new Changelog(data.changelog) + const [baseVersion, setBaseVersion] = useState(latestVersion) - const allVersions = changelog.data.map(({ version }) => version) - const changesCount = allVersions.length + const [compareVersion, setCompareVersion] = useState() const firstVersionToShow = changelog.data.findIndex( (change) => change.version === baseVersion @@ -63,50 +47,47 @@ const ChangeLogViewer = ({ ) const availableVersionsToCompareWith = allVersions.slice( - firstVersionToShow + 1, - changelog.data?.length - firstVersionToShow + firstVersionToShow + 1 ) - const getType = (type) => { + const getFormattedChangeType = ({ type, isTranslation }) => { + if (isTranslation) { + return null + } if (type === 'Bug Fixes') { - return 'Fix' + return 'Fix: ' } if (type == 'Features') { - return 'Feature' + return 'Feature: ' } - return type + return type + ': ' + } + + if (!data || loading || !baseVersion) { + return + } + + if (!data.changelog) { + return ( +
+
+ No change log available for this app. +
+
+ ) } + return ( - - -
-
Changes in
-
- - setBaseVersion(selected) - } - selected={baseVersion} - dense - > - {allVersions.map((version) => { - return ( - - ) - })} - -
+
+
+
Show changes included from:
+
setCompareVersion(selected)} - selected={versionToCompareWith} + onChange={({ selected }) => setBaseVersion(selected)} + selected={baseVersion} dense > - {availableVersionsToCompareWith.map((version) => { + {allVersions.map((version) => { return (
- - -
-
- - - - Version - - - Changes - - - {changesToShow.map((entry) => { - const { version } = entry - - return ( - - - {version} - - -
    - {entry.changeSummary.map( - (change, i) => { - if ( - change.isTranslation - ) { - return ( -
  • to
+ setCompareVersion(selected)} + selected={versionToCompareWith} + dense + > + {availableVersionsToCompareWith.map((version) => { + return ( + + ) + })} + +
+
+ + {changesToShow.map((entry) => { + const { version } = entry + + return ( + + + Version {version} + + + - - - ) - })} - - - - - + link + + + ) : null} + + ) + } + )} + + + + ) + })} +
+
+
) } +ChangeLogViewer.propTypes = { + appId: PropTypes.string, + latestVersion: PropTypes.string, +} export default ChangeLogViewer diff --git a/client/src/components/Versions/ChangeLogViewer.module.css b/client/src/components/Versions/ChangeLogViewer.module.css index 31e9ceb9..636e6edb 100644 --- a/client/src/components/Versions/ChangeLogViewer.module.css +++ b/client/src/components/Versions/ChangeLogViewer.module.css @@ -1,6 +1,13 @@ -.cardContainer { - margin: var(--spacers-dp8); - padding: var(--spacers-dp8); +@value m-medium from 'src/styles/breakpoints.css'; + +.container { + padding: var(--spacers-dp12); +} + +@media m-medium { + .container { + padding: var(--spacers-dp24); + } } .header { @@ -37,6 +44,7 @@ .versionHeader { font-size: 1.1em; + font-weight: bold; } .translation { @@ -45,10 +53,19 @@ a.changeLink { color: var(--colors-blue600); + text-decoration: underline; } a.changeLink:hover, a.changeLink:focus { color: var(--colors-blue700); - text-decoration: underline; + text-decoration: none; +} + +.disabledText { + color: var(--colors-grey700); } + +.changelogContainer :global(table) { + border: 0 !important +} \ No newline at end of file diff --git a/client/src/components/Versions/Filters/Filters.js b/client/src/components/Versions/Filters/Filters.js index 6d380d5d..ae6a9096 100644 --- a/client/src/components/Versions/Filters/Filters.js +++ b/client/src/components/Versions/Filters/Filters.js @@ -37,17 +37,26 @@ const Filters = ({
)}
-
+
- setDhisVersionFilter(selected) - } + onChange={({ selected }) => { + if (!selected) { + setDhisVersionFilter('-1') + } else { + setDhisVersionFilter(selected) + } + }} > + {dhisVersions.map((dhisVersion) => ( { return { availableChannels, channelsFilter, setChannelsFilter } } -const Versions = ({ - appId, - renderDeleteVersionButton, - showDownloadCount, - hasChangelog, -}) => { +const Versions = ({ appId, renderDeleteVersionButton, showDownloadCount }) => { const { availableChannels, channelsFilter, setChannelsFilter } = useChannels(appId) - const [dhisVersionFilter, setDhisVersionFilter] = useState('') + const [dhisVersionFilter, setDhisVersionFilter] = useState('-1') const params = useMemo( () => ({ pageSize: 5, - minDhisVersion: dhisVersionFilter - ? `lte:${dhisVersionFilter}` - : undefined, - maxDhisVersion: dhisVersionFilter - ? `gte:${dhisVersionFilter}` - : undefined, + minDhisVersion: + dhisVersionFilter != '-1' + ? `lte:${dhisVersionFilter}` + : undefined, + maxDhisVersion: + dhisVersionFilter != '-1' + ? `gte:${dhisVersionFilter}` + : undefined, channel: Array.from(channelsFilter).join(), }), [dhisVersionFilter, Array.from(channelsFilter).join()] @@ -79,23 +75,6 @@ const Versions = ({ const versions = data ?? [] - const [changelogVisible, setChangelogVisible] = useState(false) - const showChangeLog = () => { - setChangelogVisible(!changelogVisible) - } - - const onCloseChangelog = () => { - setChangelogVisible(false) - } - - const [compareVersion, setCompareVersion] = useState() - const [baseVersion, setBaseVersion] = useState(versions?.[0]?.version) - - const changeBaseVersion = (version) => { - setBaseVersion(version) - setCompareVersion() - } - if (error) { return ( @@ -113,15 +92,13 @@ const Versions = ({ } return ( -
+
{versions.length > 0 ? (
)} - {changelogVisible && ( - - )}
) } @@ -164,7 +131,6 @@ const Versions = ({ Versions.propTypes = { appId: PropTypes.string.isRequired, renderDeleteVersionButton: PropTypes.func, - hasChangelog: PropTypes.bool, } export default Versions diff --git a/client/src/components/Versions/Versions.module.css b/client/src/components/Versions/Versions.module.css index 2bcb0934..4413193f 100644 --- a/client/src/components/Versions/Versions.module.css +++ b/client/src/components/Versions/Versions.module.css @@ -1,7 +1,3 @@ -.versionsContainer { - border: 1px solid var(--colors-grey300); -} - .noVersions { display: inline-block; padding: var(--spacers-dp8); diff --git a/client/src/components/Versions/VersionsTable/VersionsTable.js b/client/src/components/Versions/VersionsTable/VersionsTable.js index 6842ff3f..401c3ea4 100644 --- a/client/src/components/Versions/VersionsTable/VersionsTable.js +++ b/client/src/components/Versions/VersionsTable/VersionsTable.js @@ -48,9 +48,9 @@ const VersionsTable = ({ Version - Channel - DHIS2 version compatibility - Upload date + Date released + DHIS2 compatibility + Release channel {showDownloadCount && ( Downloads )} @@ -61,8 +61,12 @@ const VersionsTable = ({ {versions.map((version) => ( {version.version} - - {appChannelToDisplayName[version.channel]} + + + {new Date( + version.createdAt + ).toLocaleDateString()} + {renderDhisVersionsCompatibility( @@ -70,13 +74,10 @@ const VersionsTable = ({ version.maxDhisVersion )} - - - {new Date( - version.createdAt - ).toLocaleDateString()} - + + {appChannelToDisplayName[version.channel]} + {showDownloadCount && ( {version.downloadCount} @@ -84,13 +85,12 @@ const VersionsTable = ({ )} - + Download {renderDeleteVersionButton && renderDeleteVersionButton(version)} diff --git a/client/src/components/Versions/VersionsTable/VersionsTable.module.css b/client/src/components/Versions/VersionsTable/VersionsTable.module.css index 30bb7259..39b8608e 100644 --- a/client/src/components/Versions/VersionsTable/VersionsTable.module.css +++ b/client/src/components/Versions/VersionsTable/VersionsTable.module.css @@ -12,3 +12,16 @@ .channelNameCell { color: var(--colors-grey700); } + + +a.link { + color: var(--colors-blue600); + text-decoration: underline; +} + +a.link:hover, +a.link:focus { + color: var(--colors-blue700); + text-decoration: none; +} + diff --git a/client/src/pages/AppView/AppView.js b/client/src/pages/AppView/AppView.js index eceb3cd2..923ff05a 100644 --- a/client/src/pages/AppView/AppView.js +++ b/client/src/pages/AppView/AppView.js @@ -3,12 +3,18 @@ import { CircularLoader, NoticeBox, Card, - Tag, Divider, Button, + TabBar, + Tab, + IconUser16, + IconTerminalWindow16, } from '@dhis2/ui' import classnames from 'classnames' import PropTypes from 'prop-types' +import { useHistory } from 'react-router-dom' +import PluginTag from '../../components/PluginTag/PluginTag' +import ChangeLogViewer from '../../components/Versions/ChangeLogViewer' import styles from './AppView.module.css' import config from 'config' import { useQueryV1 } from 'src/api' @@ -17,7 +23,6 @@ import AppIcon from 'src/components/AppIcon/AppIcon' import Screenshots from 'src/components/Screenshots/Screenshots' import Versions from 'src/components/Versions/Versions' import { renderDhisVersionsCompatibility } from 'src/lib/render-dhis-versions-compatibility' -import PluginTag from '../../components/PluginTag/PluginTag' const HeaderSection = ({ appName, @@ -27,43 +32,93 @@ const HeaderSection = ({ hasPlugin, pluginType, organisationSlug, -}) => ( -
- -
-

{appName}

- - by{' '} - - {appDeveloper} - - -
- {appType} - {hasPlugin && ( - + latestVersion, + sourceUrl, +}) => { + const history = useHistory() + return ( +
+ +
+

{appName}

+
+ +
+ + {appType} +
+ {hasPlugin && ( + + )} +
+
+
+
+ + + + +
+
+ + { + config.ui.appChannelToDisplayName[ + latestVersion.channel + ] + }{' '} + release v{latestVersion.version}. + + + Compatible with DHIS2{' '} + {renderDhisVersionsCompatibility( + latestVersion.minDhisVersion, + latestVersion.maxDhisVersion + )} + . + +
+ {sourceUrl && ( + <> + + Source code + + )}
-
-
-) + + ) +} HeaderSection.propTypes = { appDeveloper: PropTypes.string.isRequired, appName: PropTypes.string.isRequired, appType: PropTypes.string.isRequired, - pluginType: PropTypes.string, + organisationSlug: PropTypes.string.isRequired, hasPlugin: PropTypes.bool, + latestVersion: PropTypes.object, logoSrc: PropTypes.string, - organisationSlug: PropTypes.string.isRequired, + pluginType: PropTypes.string, + sourceUrl: PropTypes.string, } -const AboutSection = ({ appDescription, latestVersion, sourceUrl }) => ( +const AboutSection = ({ appDescription }) => (

About this app

@@ -78,40 +133,22 @@ const AboutSection = ({ appDescription, latestVersion, sourceUrl }) => ( )}
-
- - - -
- - {config.ui.appChannelToDisplayName[latestVersion.channel]}{' '} - release v{latestVersion.version}. - - - Compatible with DHIS2{' '} - {renderDhisVersionsCompatibility( - latestVersion.minDhisVersion, - latestVersion.maxDhisVersion - )} - . - -
- {sourceUrl && ( - <> - - - Source code - - - )} -
) +AboutSection.propTypes = { + appDescription: PropTypes.string, +} + const AppView = ({ match }) => { const { appId } = match.params const { data: app, error } = useQueryV1(`apps/${appId}`) + const history = useHistory() + + const selectedTab = + new URLSearchParams(window.location.search).get('tab') ?? 'about' + if (error) { return ( @@ -136,6 +173,10 @@ const AppView = ({ match }) => { const versions = app.versions.sort((a, b) => b.created - a.created) const latestVersion = versions[0] + const selectTab = (tabName) => () => { + history.push('?tab=' + tabName) + } + return ( { logoSrc={logoSrc} hasPlugin={app.hasPlugin} pluginType={app.pluginType} - /> - - - - {screenshots.length > 0 && ( + + + About + + + Previous releases + + + Change log + + {/* + More apps by {appDeveloper} + */} + + {selectedTab === 'about' && ( <> -
-

Screenshots

- -
- + {screenshots.length > 0 && ( + <> +
+

+ Screenshots +

+ +
+ + )} + )} -
-

- All versions of this application -

- -
+ + {selectedTab === 'previous-releases' && ( +
+ +
+ )} + + {selectedTab === 'changelog' && ( +
+ +
+ )}
) } diff --git a/client/src/pages/AppView/AppView.module.css b/client/src/pages/AppView/AppView.module.css index 1108b54f..d10b97f2 100644 --- a/client/src/pages/AppView/AppView.module.css +++ b/client/src/pages/AppView/AppView.module.css @@ -18,7 +18,7 @@ .appCardHeader { display: grid; - grid-template-columns: 72px auto 67px; + grid-template-columns: 72px auto 267px; grid-gap: var(--spacers-dp12); } @@ -29,25 +29,36 @@ margin-bottom: 6px; } -.appCardDeveloper { - display: block; +.appTags { + flex-direction: row; + display: flex; + align-items: center; + gap: var(--spacers-dp16) +} + +.tagWithIcon { + display: flex; + gap: var(--spacers-dp4); font-size: 16px; - margin-bottom: var(--spacers-dp8); color: var(--colors-grey800); } +.topActionButtons { + display: flex; + flex-direction: column; + gap: var(--spacers-dp4); +} + +.topActionButtons :global(button) { + width: 100%; +} + .appCardType { font-size: 13px; color: var(--colors-grey700); margin-right: 5px; } -.appCardTypeContainer { - flex-direction: 'row'; - display: 'flex'; - align-items: 'center'; -} - .appCardHeading { font-weight: 500; font-size: 16px; @@ -77,7 +88,7 @@ .latestVersionDescription { margin-top: var(--spacers-dp8); - margin-bottom: 0; + margin-bottom: var(--spacers-dp8); } .latestVersionDescription span { @@ -109,3 +120,11 @@ .sourceUrl:focus { color: var(--colors-grey900); } + +.organisationLink { + color: var(--colors-grey800); +} + +.organisationLink:hover { + text-decoration: underline; +} \ No newline at end of file