Skip to content

Commit

Permalink
Merge pull request #8 from EyeSeeTea/development
Browse files Browse the repository at this point in the history
Release 2.2.0
  • Loading branch information
adrianq authored Mar 28, 2023
2 parents d848c60 + 667f485 commit dc57d17
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 72 deletions.
27 changes: 17 additions & 10 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
<div class="row">
<button onclick="window.print()" class="btn btn-primary hidden-print">{{ 'PRINT' | translate }}</button>
<button type="button" id="export-button" ng-click="exportToTable('tallysheetForm')"
class="btn btn-success hidden-print" export-button="{{selectedDatasets}}"
ng-disabled="progressbarDisplayed">
class="btn btn-success hidden-print"
export-button="{{ {selectedDatasets: selectedDatasets, selectedLocales: selectedLocales, progressbarDisplayed: progressbarDisplayed} }}">
{{ 'EXPORT_EXCEL' | translate}}</button>
<span ng-show="exporting" class="loading-icon">
<span class="glyphicon glyphicon-hourglass"></span>
Expand All @@ -47,19 +47,21 @@
<!-- INCLUDE HEADERS -->
<div class="row include-headers">
<label id="includeHeaders">{{ 'HEADERS' | translate }}</label>
<input type="checkbox" ng-model="includeHeaders" aria-labelledby="includeHeaders">
<input type="checkbox" ng-model="includeHeaders" aria-labelledby="includeHeaders"
ng-disabled="exporting || progressbarDisplayed">
</div>

<!-- SELECT ALL -->
<div class="row select-all">
<div>
<label id="selectAllLangs">{{ 'SELECT_ALL_LANGUAGES' | translate }}</label>
<input type="checkbox" ng-click="updateLangs()" ng-model="selectAllLangs"
aria-labelledby="selectAllLangs">
aria-labelledby="selectAllLangs" ng-disabled="exporting || progressbarDisplayed">
</div>
<div>
<label id="selectAllDatasets">{{ 'SELECT_ALL_DATASETS' | translate }}</label>
<input type="checkbox" ng-model="selectAllDatasets" aria-labelledby="selectAllDatasets">
<input type="checkbox" ng-model="selectAllDatasets" aria-labelledby="selectAllDatasets"
ng-disabled="progressbarDisplayed || exporting">
</div>
</div>

Expand All @@ -68,23 +70,28 @@
<div>
<form id="datasetSelectorForm" class="inline" ng-show="!selectAllDatasets">
<select title="Nothing selected" name="dataset" class="selectpicker hidden-print"
data-live-search="true" multiple>
data-live-search="true" multiple ng-disabled="exporting || progressbarDisplayed"
ng-class="(exporting || progressbarDisplayed) ? 'disabled' : ''">
<option disabled value="0">{{ 'SELECT_DATASET' | translate }}</option>
<option ng-repeat="dataset in datasets | orderBy : 'displayName'" ng-value="dataset.id"
on-finish-render="ngRepeatFinished">{{dataset.displayName}}</option>
</select>
</form>
<form id="languageSelectorForm" class="inline" ng-show="!selectAllLangs">
<select title="Nothing selected" name="language" class="selectpicker hidden-print"
data-live-search="true" multiple>
data-live-search="true" multiple ng-disabled="exporting || progressbarDisplayed"
ng-class="(exporting || progressbarDisplayed) ? 'disabled' : ''">
<option disabled value="0">{{ 'SELECT_LANGUAGE' | translate }}</option>
<option ng-repeat="language in availableLanguages | orderBy : 'locale'"
ng-value="language.locale" on-finish-render="ngRepeatFinished">{{ language.name }}
</option>
ng-value="language.locale" on-finish-render="ngRepeatFinished"
ng-selected="preferredLanguage===language.locale&&selectedDatasets.length>0">
{{language.name}}</option>
</select>
</form>
</div>
<button ng-click="clearForm()" class="btn btn-default hidden-print">
<button ng-click="clearForm()" class="btn btn-default hidden-print"
ng-class="(exporting || progressbarDisplayed) ? 'disabled' : ''"
ng-disable="exporting || progressbarDisplayed">
<span class="glyphicon glyphicon-remove"></span>
</button>
<div>
Expand Down
2 changes: 2 additions & 0 deletions languages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"HOME": "Inicio",
"SELECT_DATASET": "Selecciona un set de datos",
"SELECT_LANGUAGE": "Selecciona un idioma",
"SELECT_ALL_LANGUAGES": "Seleccionar todos los idiomas",
"SELECT_ALL_DATASETS": "Seleccionar todos los sets de datos",
"ADD_FORM": "Añadir formulario",
"HEADERS": "Incluir cabeceras",
"FACILITY": "Centro sanitario",
Expand Down
2 changes: 2 additions & 0 deletions languages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"HOME": "Accueil",
"SELECT_DATASET": "Sélectionnez un ensemble de données",
"SELECT_LANGUAGE": "Sélectionnez une langue",
"SELECT_ALL_LANGUAGES": "Sélectionnez toutes les langues",
"SELECT_ALL_DATASETS": "Sélectionner tous les ensembles de données",
"ADD_FORM": "Ajouter formulaire",
"HEADERS": "Ajouter en-tête",
"FACILITY": "Structure de santé",
Expand Down
2 changes: 2 additions & 0 deletions languages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
"HOME": "Principal",
"SELECT_DATASET": "Selecione um conjunto de dados",
"SELECT_LANGUAGE": "Selecione um idioma",
"SELECT_ALL_LANGUAGES": "Selecione todos os idiomas",
"SELECT_ALL_DATASETS": "Selecione todos os conjuntos de dados",
"ADD_FORM": "Adicionar forma",
"HEADERS": "Adicionar cabeçalho",
"FACILITY": "Centro sanitário",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.1.0",
"version": "2.2.0",
"name": "hmis-tally-sheets",
"description": "HMIS Tally sheets",
"author": "MSF OCBA, EyeSeeTea team",
Expand Down
14 changes: 7 additions & 7 deletions src/data/repositories/DataSetsDhis2Repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,31 @@ export class DataSetsDhis2Repository {
}
}

const common = "id,displayFormName,translations";
const common = "id,name,displayFormName,translations";

const fields = `
id,
name,
${common},
formName,
displayName,
formType,
displayFormName,
sections[
id,
translations,
name,
displayName,
description,
categoryCombos[
id,
categories[categoryOptions[${common}]],
categoryOptionCombos[
id,
name,
displayFormName,
categoryOptions[${common}]
]
],
dataElements[${common},categoryCombo],
dataElements[${common},formName,categoryCombo],
greyedFields[dataElement,categoryOptionCombo]
],
dataSetElements[categoryCombo[id,displayName,categories[*],categoryOptionCombos[*]],dataElement[${common}]],
translations
dataSetElements[categoryCombo[id,displayName,categories[*],categoryOptionCombos[*]],dataElement[${common},formName]],
`.replaceAll(/\s/g, "");
18 changes: 9 additions & 9 deletions src/data/repositories/DataSetsExportSpreadsheetRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ export class DataSetsExportSpreadsheetRepository {
if (dataSet.formType === "SECTION" || dataSet.formType === "DEFAULT") {
return [
{
name: dataSet.pickedTranslations
? `${dataSet.name.trim()}_${dataSet.pickedTranslations}.xlsx`
: `${dataSet.name.trim()}.xlsx`,
name: `${dataSet.displayFormName.trim()}`,
blob: XlsxPopulate.fromBlankAsync().then(workbook => exportDataSet(workbook, dataSet)),
},
];
Expand Down Expand Up @@ -64,18 +62,19 @@ const styles = {
},
};

function populateHeaders(sheet, header) {
function populateHeaders(sheet, header, title) {
sheet.cell("A1").value(header.healthFacility).style(styles.titleStyle);
sheet.cell("A2").value(header.reportingPeriod).style({ bold: true, fontSize: 18 });
sheet.cell("A3").value(header.dataSetName).style(styles.titleStyle);
sheet.cell("A3").value(title).style(styles.titleStyle);
}

function populateDefault(sheet, dataSet) {
if (dataSet.headers) populateHeaders(sheet, dataSet.headers);
if (dataSet.headers)
populateHeaders(sheet, dataSet.headers, dataSet.displayFormName ?? dataSet.headers.dataSetName);
sheet.cell("A4").value(dataSet.displayFormName).style(styles.titleStyle);
sheet.cell("B6").value("Value");
sheet.row(6).style(styles.categoryHeaderStyle);
_.sortBy(dataSet.dataSetElements, ({ displayFormName }) => displayFormName).forEach((de, idx) =>
dataSet.dataSetElements.forEach((de, idx) =>
sheet
.row(7 + idx) //(6 + 1 cause idx starts on 0)
.cell(1)
Expand All @@ -90,7 +89,8 @@ function populateDefault(sheet, dataSet) {
}

function populateSections(sheet, dataSet) {
if (dataSet.headers) populateHeaders(sheet, dataSet.headers);
if (dataSet.headers)
populateHeaders(sheet, dataSet.headers, dataSet.displayFormName ?? dataSet.headers.dataSetName);
let row = 3;
_.range(0, dataSet.sections.length).forEach(i => {
const section = dataSet.sections[i];
Expand Down Expand Up @@ -136,7 +136,7 @@ function addSection(sheet, section, row) {

const cocIds = categoryCombo.categoryOptionCombos.map(({ id }) => id);

_.sortBy(categoryCombo.dataElements, ({ displayFormName }) => displayFormName).forEach(de => {
categoryCombo.dataElements.forEach(de => {
sheet.row(++row).cell(1).value(de.displayFormName).style(styles.dataElementStyle);

if (!_.isEmpty(categoryCombo.greyedFields)) {
Expand Down
97 changes: 75 additions & 22 deletions src/domain/usecases/ExportDatasetsUseCase.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,38 @@ export class ExportDatasetsUseCase {
this.dataSetsExportSpreadsheetRepository = dataSetsExportSpreadsheetRepository;
}

execute($resource, dataSetsIds, headers, locales, removedSections) {
execute($resource, dataSetsIds, _headers, locales, removedSections) {
const translations$ = _.fromPairs(
_.uniq([...locales, "en"]).map(locale => [
locale,
fetch(`${location}/languages/${locale}.json`)
.then(response => response.json())
.catch(() => undefined),
])
);
const promises$ = Object.keys(translations$).map(prop => translations$[prop] ?? Promise.resolve(null));
const translations = Promise.all(promises$).then(results => {
return Object.keys(translations$).reduce(
(acc, task, i) => Object.assign(acc, { [Object.keys(translations$)[i]]: results[i] }),
{}
);
});

return this.dataSetsDhis2Repository
.get($resource, dataSetsIds)
.$promise.then(({ dataSets }) => {
.$promise.then(({ dataSets }) => translations.then(translations => ({ translations, dataSets })))
.then(({ dataSets, translations }) => {
const dataSetsWithoutCommentsAndRemovedSections = dataSets.map(dataSet => ({
...dataSet,
sections: dataSet.sections.filter(
section =>
!(
section.name.toLowerCase().includes("comments") ||
section.displayName.toLowerCase().includes("comments") ||
section.displayName.toLowerCase().includes("comentarios") ||
section.displayName.toLowerCase().includes("commentaires") ||
section.displayName.toLowerCase().includes("comentários") ||
section.displayName.toLowerCase().includes("notas") ||
removedSections.includes(section.id)
)
),
Expand Down Expand Up @@ -44,29 +66,43 @@ export class ExportDatasetsUseCase {

const pickedTranslations = overridedDataSets.map(dataSet => ({
...dataSet,
pickedTranslations: _.intersection(
locales,
dataSet.translations
pickedTranslations: _.intersection(locales, [
"en", //add English for default
...dataSet.translations
.filter(translation => translation.property === "NAME")
.map(translation => translation.locale.split("_")[0])
),
.map(translation => translation.locale.split("_")[0]),
]),
}));

const translatedDatasets = pickedTranslations.flatMap(mapDataSetTranslations);

const mappedDatasets = getDataSets([...translatedDatasets, ...overridedDataSets]);
const mappedDatasets = getDataSets(translatedDatasets);

const dataSetsWithHeaders = mappedDatasets.map(dataSet => ({
...dataSet,
headers: headers.find(({ id }) => id === dataSet.id),
}));
const dataSetsWithHeaders = mappedDatasets.map(dataSet => {
const healthFacility =
translations[dataSet.pickedTranslations]?.FACILITY ?? translations.en.FACILITY;
const reportingPeriod = translations[dataSet.pickedTranslations]?.PERIOD ?? translations.en.PERIOD;
return {
...dataSet,
headers: {
healthFacility: healthFacility ? healthFacility + ": " : "",
reportingPeriod: reportingPeriod ? reportingPeriod + ": " : "",
},
};
});

return this.dataSetsExportSpreadsheetRepository.createFiles(dataSetsWithHeaders);
})
.then(blobFiles => {
if (blobFiles.length > 1) {
const zip = new JSZip();
blobFiles.forEach(file => zip.file(sanitizeFileName(file.name), file.blob));
const names = [];
blobFiles.forEach(file => {
const name = sanitizeFileName(file.name);
const idx = names.filter(s => s === name).length;
zip.file(name + (idx ? ` (${idx})` : "") + ".xlsx", file.blob);
names.push(name);
});

return zip.generateAsync({ type: "blob" }).then(blob => {
saveAs(blob, "MSF-OCBA HMIS.zip");
Expand All @@ -76,7 +112,8 @@ export class ExportDatasetsUseCase {

return file?.blob.then(blob => saveAs(blob, sanitizeFileName(file.name)));
}
});
})
.catch(err => console.error(err));
}
}

Expand Down Expand Up @@ -192,31 +229,43 @@ function mapCategoryOption(categoryOption, locale) {
displayFormName:
getTranslationValue(categoryOption.translations, locale, "FORM_NAME") ??
getTranslationValue(categoryOption.translations, locale, "NAME") ??
categoryOption.displayFormName,
(locale === "en" ? categoryOption.name : categoryOption.displayFormName),
};
}

function mapDataSetTranslations(dataSet) {
return dataSet.pickedTranslations.map(locale => {
const mappedDataset = {
...dataSet,
displayFormName: getTranslationValue(dataSet.translations, locale) ?? dataSet.displayFormName,
displayFormName:
getTranslationValue(dataSet.translations, locale) ??
(locale === "en" ? dataSet.formName ?? dataSet.name : dataSet.displayFormName),
sections: dataSet.sections.map(section => ({
...section,
//section does not have description available to translate??
displayName: getTranslationValue(section.translations, locale) ?? section.displayName,
displayName:
getTranslationValue(section.translations, locale) ??
(locale === "en" ? section.name : section.displayName),
categoryCombos: section.categoryCombos.map(categoryCombo => ({
...categoryCombo,
categories: categoryCombo.categories.map(category => ({
categoryOptions: category.categoryOptions.map(co => mapCategoryOption(co, locale)),
})),
categoryOptionCombos: categoryCombo.categoryOptionCombos.map(coc => {
const categoryOptions = coc.categoryOptions.map(co => mapCategoryOption(co, locale));
const ids = coc.displayFormName
const ids = (locale === "en" ? coc.name : coc.displayFormName)
.split(", ")
.map(dco => coc.categoryOptions.find(co => co.displayFormName === dco)?.id);
.map(
dco =>
coc.categoryOptions.find(
co => (locale === "en" ? co.name : co.displayFormName) === dco
)?.id
);
const displayFormName = ids
.map(id => categoryOptions.find(co => co.id === id)?.displayFormName)
.map(id => {
const co = categoryOptions.find(co => co.id === id);
return locale === "en" ? co?.name : co?.displayFormName;
})
.join(", ");

return {
Expand All @@ -231,7 +280,7 @@ function mapDataSetTranslations(dataSet) {
displayFormName:
getTranslationValue(de.translations, locale, "FORM_NAME") ??
getTranslationValue(de.translations, locale, "NAME") ??
de.displayFormName,
(locale === "en" ? de.formName ?? de.name : de.displayFormName),
})),
})),
dataSetElements: dataSet.dataSetElements.map(dse => ({
Expand All @@ -241,11 +290,15 @@ function mapDataSetTranslations(dataSet) {
displayFormName:
getTranslationValue(dse.dataElement.translations, locale, "FORM_NAME") ??
getTranslationValue(dse.dataElement.translations, locale, "NAME") ??
dse.dataElement.displayFormName,
(locale === "en"
? dse.dataElement.formName ?? dse.dataElement.name
: dse.dataElement.displayFormName),
},
})),
};

return { ...mappedDataset, pickedTranslations: locale };
});
}

const location = window.location.href.substring(0, window.location.href.lastIndexOf("/"));
8 changes: 4 additions & 4 deletions src/views/formDatasetView.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="dataset-form" ng-controller="formDatasetCtrl">
<div ng-show="includeHeaders">
<h3><input type="text" class="dsTitle" ng-model="headers.healthFacility" /></h3>
<h3><input type="text" class="dsTitle" ng-model="headers.reportingPeriod" /></h3>
<h3><input type="text" class="dsTitle" ng-model="headers.healthFacility" readonly /></h3>
<h3><input type="text" class="dsTitle" ng-model="headers.reportingPeriod" readonly /></h3>
</div>
<h2><input id="title" class="dsTitle" ng-model="headers.dataSetName" /></h2>
<h2><input id="title" class="dsTitle" ng-model="headers.dataSetName" readonly /></h2>
<div id="dataset-{{ dataset.id }}">
<ng-bind-html ng-bind-html="deliberatelyTrustDangerousSnippet()" on-finish-render></ng-bind-html>
</div>
</div>
</div>
Loading

0 comments on commit dc57d17

Please sign in to comment.