Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 2 additions & 67 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2025-11-17T22:16:03.463Z\n"
"PO-Revision-Date: 2025-11-17T22:16:03.464Z\n"
"POT-Creation-Date: 2025-11-26T13:10:18.388Z\n"
"PO-Revision-Date: 2025-11-26T13:10:18.389Z\n"

msgid "Failed to load: {{error}}"
msgstr "Failed to load: {{error}}"
Expand Down Expand Up @@ -911,68 +911,3 @@ msgstr "Clean idle jobs after (in milliseconds)"

msgid "Use centroids for organisation unit polygons in event analytics"
msgstr "Use centroids for organisation unit polygons in event analytics"

msgid ""
"When enabled, event analytics requests that normally return polygons for "
"organisation units will return centroids instead. Improves performance for "
"large datasets."
msgstr ""
"When enabled, event analytics requests that normally return polygons for "
"organisation units will return centroids instead. Improves performance for "
"large datasets."

msgctxt "Application title"
msgid "__MANIFEST_APP_TITLE"
msgstr "System Settings"

msgctxt "Application description"
msgid "__MANIFEST_APP_DESCRIPTION"
msgstr ""

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_General"
msgstr "General"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Analytics"
msgstr "Analytics"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Server"
msgstr "Server"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Appearance"
msgstr "Appearance"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Email"
msgstr "Email"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Access"
msgstr "Access"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Calendar"
msgstr "Calendar"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Data import settings"
msgstr "Data import settings"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Synchronization"
msgstr "Synchronization"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Scheduled job settings"
msgstr "Scheduled job settings"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_Notification settings"
msgstr "Notification settings"

msgctxt "Title for shortcut used by command palette"
msgid "__MANIFEST_SHORTCUT_OAuth2 clients"
msgstr "OAuth2 clients"
199 changes: 199 additions & 0 deletions src/period-types/PeriodTypes.component.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import { useDataQuery } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import { CenteredContent, CircularLoader } from '@dhis2/ui'
import CheckboxMaterial from 'material-ui/Checkbox'
import React from 'react'
import styles from './PeriodTypes.module.css'

const query = {
periodTypes: {
resource: 'periodTypes',
},
dataOutputPeriodTypes: {
resource: 'configuration/dataOutputPeriodTypes',
},
}

// Format period type names for display
const formatPeriodTypeName = (name) => {
// Handle Weekly variants
if (name === 'Weekly') {
return i18n.t('Weekly')
}
if (name.startsWith('Weekly')) {
const day = name.replace('Weekly', '')
return i18n.t('Weekly (start {{day}})', { day })
}

// Handle Financial year variants
if (name.startsWith('Financial')) {
const monthAbbrev = name.replace('Financial', '')
const monthMap = {
April: 'April',
July: 'July',
Oct: 'October',
Nov: 'November',
}
const month = monthMap[monthAbbrev] || monthAbbrev
return i18n.t('Financial year (start {{month}})', { month })
}

// Handle Six-monthly variants
if (name === 'SixMonthly') {
return i18n.t('Six-monthly')
}
if (name.startsWith('SixMonthly')) {
const monthAbbrev = name.replace('SixMonthly', '')
const monthMap = {
April: 'April',
Nov: 'November',
}
const month = monthMap[monthAbbrev] || monthAbbrev
return i18n.t('Six-monthly (start {{month}})', { month })
}

// Handle Quarterly variants
if (name === 'Quarterly') {
return i18n.t('Quarterly')
}
if (name.startsWith('Quarterly')) {
const monthAbbrev = name.replace('Quarterly', '')
const monthMap = {
Nov: 'November',
}
const month = monthMap[monthAbbrev] || monthAbbrev
return i18n.t('Quarterly (start {{month}})', { month })
}

// Handle other common types
const simpleLabels = {
Daily: i18n.t('Daily'),
Monthly: i18n.t('Monthly'),
BiMonthly: i18n.t('Bi-monthly'),
Yearly: i18n.t('Yearly'),
BiWeekly: i18n.t('Bi-weekly'),
}

if (simpleLabels[name]) {
return simpleLabels[name]
}

// Fallback: add spaces before capital letters
return name.replace(/([A-Z])/g, ' $1').trim()

Check warning on line 82 in src/period-types/PeriodTypes.component.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=dhis2_settings-app&issues=AZrEfJPkot81ScfmQgQF&open=AZrEfJPkot81ScfmQgQF&pullRequest=1451
}

// Map frequencyOrder to group labels
const getGroupLabel = (frequencyOrder) => {
if (frequencyOrder === 1) {
return i18n.t('Days')
}
if (frequencyOrder === 7) {
return i18n.t('Weeks')
}
if (frequencyOrder === 14) {
return i18n.t('Bi-weeks')
}
if (frequencyOrder === 30) {
return i18n.t('Months')
}
if (frequencyOrder === 60) {
return i18n.t('Bi-months')
}
if (frequencyOrder === 91) {
return i18n.t('Quarters')
}
if (frequencyOrder === 182) {
return i18n.t('Six months')
}
if (frequencyOrder === 365) {
return i18n.t('Years')
}
return i18n.t('Other')
}

// Group period types by frequencyOrder
const groupByFrequency = (periodTypes) => {
const groups = {}
periodTypes.forEach((pt) => {
const freq = pt.frequencyOrder
if (!groups[freq]) {
groups[freq] = {
label: getGroupLabel(freq),
frequencyOrder: freq,
periodTypes: [],
}
}
groups[freq].periodTypes.push(pt)
})
// Sort groups by frequencyOrder and return as array
return Object.values(groups).sort(
(a, b) => a.frequencyOrder - b.frequencyOrder
)
}

const PeriodTypes = () => {
const { loading, error, data } = useDataQuery(query)

if (loading) {
return (
<CenteredContent>
<CircularLoader />
</CenteredContent>
)
}

if (error) {
return (
<div className={styles.error}>
{i18n.t('Failed to load period types: {{error}}', {
error: error.message,
nsSeparator: '-:-',
})}
</div>
)
}

const allPeriodTypes = data?.periodTypes?.periodTypes || []
const allowedPeriodTypes = data?.dataOutputPeriodTypes || []

// Create a Set of allowed period type names for quick lookup
const allowedSet = new Set(allowedPeriodTypes.map((pt) => pt.name))

// Group period types by frequency
const groupedPeriodTypes = groupByFrequency(allPeriodTypes)

return (
<div className={styles.wrapper}>
<p className={styles.sectionLabel}>
{i18n.t('Period types available in analytics apps')}
</p>
<div className={styles.groupsWrapper}>
{groupedPeriodTypes.map((group) => (
<div key={group.frequencyOrder} className={styles.group}>
<p className={styles.groupLabel}>{group.label}</p>
<div className={styles.checkboxList}>
{group.periodTypes.map((periodType) => (
<div
key={periodType.name}
className={styles.checkboxItem}
>
<CheckboxMaterial
checked={allowedSet.has(
periodType.name
)}
label={formatPeriodTypeName(
periodType.name
)}
onCheck={() => {}}
/>
</div>
))}
</div>
</div>
))}
</div>
</div>
)
}

export default PeriodTypes
45 changes: 45 additions & 0 deletions src/period-types/PeriodTypes.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
.wrapper {
margin: 16px 0;
}

.sectionLabel {
color: var(--colors-grey800);
font-size: 12px;
margin: 0;
}

.groupsWrapper {
margin: 8px 0;
padding: 12px;
background: var(--colors-grey050);
border: 1px solid var(--colors-grey200);
border-radius: 7px;
display: flex;
flex-direction: column;
gap: 24px;
}

.groupLabel {
font-size: 13px;
font-weight: 500;
color: var(--colors-grey600);
margin: 0px 0 8px 0;
}

.checkboxList {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 6px 24px;
padding-left: 8px;
width: 100%;
}

.checkboxItem {
min-width: 0;
font-size: 15px;
}

.error {
color: var(--colors-red600);
padding: 16px;
}
14 changes: 1 addition & 13 deletions src/settingsCategories.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,7 @@ export const categories = {
setting: 'keyAnalysisDigitGroupSeparator',
},
{
setting: 'keyHideDailyPeriods',
},
{
setting: 'keyHideWeeklyPeriods',
},
{
setting: 'keyHideBiWeeklyPeriods',
},
{
setting: 'keyHideMonthlyPeriods',
},
{
setting: 'keyHideBiMonthlyPeriods',
setting: 'periodTypes',
},
{
setting: 'analyticsFinancialYearStart',
Expand Down
6 changes: 6 additions & 0 deletions src/settingsFields.component.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import metadataSettings from './metadata-settings/metadataSettings.component.jsx'
import Oauth2ClientEditor from './oauth2-client-editor/OAuth2ClientEditor.component.jsx'
import Oauth2ClientEditor41 from './oauth2-client-editor-41/OAuth2ClientEditor.component.jsx'
import PeriodTypes from './period-types/PeriodTypes.component.jsx'
import settingsActions from './settingsActions.js'
import { categories } from './settingsCategories.js'
import classes from './SettingsFields.module.css'
Expand Down Expand Up @@ -306,6 +307,11 @@
component: metadataSettings,
})

case 'periodTypes':
return Object.assign({}, fieldBase, {

Check warning on line 311 in src/settingsFields.component.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use an object spread instead of `Object.assign` eg: `{ ...foo }`.

See more on https://sonarcloud.io/project/issues?id=dhis2_settings-app&issues=AZrEfJSiot81ScfmQgQG&open=AZrEfJSiot81ScfmQgQG&pullRequest=1451
component: PeriodTypes,
})

default:
console.warn(
`Unknown control type "${mapping.type}" encountered for field "${key}"`
Expand Down
23 changes: 3 additions & 20 deletions src/settingsKeyMapping.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,26 +177,9 @@ const settingsKeyMapping = {
LAST_5_FINANCIAL_YEARS: i18n.t('Last 5 financial years'),
},
},
keyHideDailyPeriods: {
label: i18n.t('Hide daily periods'),
sectionLabel: i18n.t('Hidden period types in analytics apps'),
type: 'checkbox',
},
keyHideWeeklyPeriods: {
label: i18n.t('Hide weekly periods'),
type: 'checkbox',
},
keyHideBiWeeklyPeriods: {
label: i18n.t('Hide biweekly periods'),
type: 'checkbox',
},
keyHideMonthlyPeriods: {
label: i18n.t('Hide monthly periods'),
type: 'checkbox',
},
keyHideBiMonthlyPeriods: {
label: i18n.t('Hide bimonthly periods'),
type: 'checkbox',
periodTypes: {
type: 'periodTypes',
searchLabels: [i18n.t('Period types'), i18n.t('Allowed period types')],
},
analyticsFinancialYearStart: {
label: i18n.t('Financial year relative period start month'),
Expand Down
Loading