Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) O3-4001: Add a dummy schema template, building blocks for Nav group and its dashboards #2

Merged
merged 4 commits into from
Sep 26, 2024
Merged
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
2 changes: 1 addition & 1 deletion i18next-parser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ module.exports = {
lineEnding: 'auto',
// Control the line ending. See options at https://github.com/ryanve/eol

locales: ['en'],
locales: ['en', 'fr', 'am', 'es', 'he', 'km'],
// An array of the locales in your applications

namespaceSeparator: ':',
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@
"dependencies": {
"@carbon/react": "^1.47.0",
"ajv": "^8.17.1",
"classnames": "^2.5.1",
"dotenv": "^16.4.5",
"file-loader": "^6.2.0",
"fuzzy": "^0.1.3",
"lodash-es": "^4.17.21",
"react-ace": "^11.0.1",
"sass": "^1.67.0"
"sass": "^1.67.0",
"uuid": "^10.0.0"
},
"peerDependencies": {
"@openmrs/esm-framework": "*",
Expand All @@ -79,6 +81,7 @@
"@types/jest": "^29.5.12",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^10",
"@types/webpack-env": "^1.18.5",
"@typescript-eslint/eslint-plugin": "^7.9.0",
"@typescript-eslint/parser": "^6.21.0",
Expand All @@ -90,7 +93,7 @@
"eslint-plugin-testing-library": "^6.2.2",
"husky": "^8.0.3",
"i18next": "^23.11.4",
"i18next-parser": "^8.13.0",
"i18next-parser": "^9.0.2",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-cli": "^29.7.0",
Expand All @@ -113,5 +116,8 @@
"*.{ts,tsx}": "eslint --cache --fix --max-warnings 0",
"*.{css,scss,ts,tsx}": "prettier --write --list-different"
},
"resolutions": {
"cheerio": "^1.0.0-rc.5"
},
"packageManager": "[email protected]"
}
168 changes: 124 additions & 44 deletions src/components/dashboard/dashboard.component.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { useTranslation, type TFunction } from 'react-i18next';
import {
Button,
Expand All @@ -18,43 +18,41 @@ import {
Tile,
} from '@carbon/react';
import { Add, Download, Edit, TrashCan } from '@carbon/react/icons';
import { type KeyedMutator } from 'swr';
import { ContentPackagesBuilderPagination } from '../pagination';

import Header from '../header/header.component';
import styles from './dashboard.scss';
import { mockContentPackages } from '../../../__mocks__/content-packages.mock';
import { navigate, useLayoutType, usePagination } from '@openmrs/esm-framework';

type Mutator = KeyedMutator<{
data: {
results: Array<any>;
};
}>;
import { navigate, useLayoutType, usePagination, ConfigurableLink, showModal } from '@openmrs/esm-framework';
import { getAllPackagesFromLocalStorage, deletePackageFromLocalStorage } from '../../utils';

interface ActionButtonsProps {
clinicalViews: any;
mutate: Mutator;
responsiveSize: string;
clinicalViewKey: string;
t: TFunction;
onEdit: (key: string) => void;
onDelete: (key: string) => void;
onDownload: (key: string) => void;
}

function ActionButtons({ clinicalViews, mutate, responsiveSize, t }: ActionButtonsProps) {
function ActionButtons({ responsiveSize, clinicalViewKey, t, onDelete, onDownload, onEdit }: ActionButtonsProps) {
const defaultEnterDelayInMs = 300;

const launchDeleteClinicalViewsPackageModal = {};
const launchDeleteClinicalViewsPackageModal = () => {
const dispose = showModal('delete-clinicalView-modal', {
closeModal: () => dispose(),
clinicalViewKey,
onDelete,
});
};

const EditButton = () => {
return (
<IconButton
enterDelayMs={defaultEnterDelayInMs}
kind="ghost"
label={t('editSchema', 'Edit schema')}
onClick={() =>
navigate({
to: `${window.spaBase}/clinical-views-builder/edit/${clinicalViews}`,
})
}
onClick={onEdit}
size={responsiveSize}
>
<Edit />
Expand All @@ -64,11 +62,12 @@ function ActionButtons({ clinicalViews, mutate, responsiveSize, t }: ActionButto

const DownloadSchemaButton = () => {
return (
<a download={`${clinicalViews}.json`} href="#">
<a download={`${clinicalViewKey}.json`} href="#">
<IconButton
enterDelayMs={defaultEnterDelayInMs}
kind="ghost"
label={t('downloadSchema', 'Download schema')}
onClick={onDownload}
size={responsiveSize}
>
<Download />
Expand Down Expand Up @@ -100,48 +99,128 @@ function ActionButtons({ clinicalViews, mutate, responsiveSize, t }: ActionButto
);
}

function ContentPackagesList({ contentPackages, isValidating, mutate, t }: any) {
function ContentPackagesList({ isValidating, t }: any) {
const pageSize = 10;
const isTablet = useLayoutType() === 'tablet';
const responsiveSize = isTablet ? 'lg' : 'sm';
const [searchString, setSearchString] = useState('');
const [clinicalViews, setClinicalViews] = useState<any[]>([]);

useEffect(() => {
const packages = getAllPackagesFromLocalStorage();
setClinicalViews(Object.entries(packages).map(([key, value]) => ({ key, ...(value as object) })));
}, []);

const filteredViews = useMemo(() => {
const searchTerm = searchString.trim().toLowerCase();
return clinicalViews.filter((pkg) => pkg.key.toLowerCase().includes(searchTerm));
}, [clinicalViews, searchString]);

const handleEdit = (packageKey: string) => {
navigate({
to: `${window.spaBase}/clinical-views-builder/edit/${packageKey}`,
});
};

const handleDelete = (packageKey: string) => {
deletePackageFromLocalStorage(packageKey);
setClinicalViews(clinicalViews.filter((pkg) => pkg.key !== packageKey));
};

const tableHeaders = [
{
header: t('packageName', 'Package name'),
key: 'key',
header: t('name', 'Name'),
key: 'name',
},
{
header: t('dashboards', 'Dashboards'),
key: 'dashboards',
},
{
header: t('schemaActions', 'Schema actions'),
key: 'actions',
},
];

const searchResults = useMemo(() => {
const searchTerm = searchString?.trim().toLowerCase();
const { paginated, goTo, results, currentPage } = usePagination(filteredViews, pageSize);

const handleDownload = (key) => {
const schema = localStorage.getItem(`packageJSON_${key}`);
if (schema) {
const blob = new Blob([schema], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${key}.json`;
a.click();
} else {
console.error('No schema found to download.');
}
};

const getNavGroupTitle = (schema) => {
const packageSlotKey = 'patient-chart-dashboard-slot';
const packageConfig = schema?.['@openmrs/esm-patient-chart-app']?.extensionSlots[packageSlotKey];

if (searchTerm) {
return mockContentPackages.filter((contentPackage) =>
Object.keys(contentPackage).some((key) => key.toLowerCase().includes(searchTerm)),
);
if (packageConfig) {
const navGroupKey = packageConfig?.add[0];
const navGroupConfig = packageConfig?.configure[navGroupKey];
return navGroupConfig?.title || 'Unnamed Clinical View';
}

return mockContentPackages;
}, [searchString]);
return 'default_schema_name';
};

const { paginated, goTo, results, currentPage } = usePagination(searchResults, pageSize);
const getDashboardTitles = (schema) => {
const packageSlotKey = 'patient-chart-dashboard-slot';

const tableRows = Array.from(results, (result) => {
return Object.keys(result).map((key) => {
const contentPackage = result[key];
return {
...contentPackage,
id: key,
key: key,
actions: <ActionButtons clinicalViews={contentPackage} mutate={mutate} responsiveSize={responsiveSize} t={t} />,
};
});
}).flat();
const packageConfig = schema?.['@openmrs/esm-patient-chart-app']?.extensionSlots?.[packageSlotKey];

const navGroupKey = packageConfig?.add?.[0];
const navGroupConfig = packageConfig?.configure?.[navGroupKey];

const submenuSlotKey = navGroupConfig?.slotName;
const submenuConfig = schema?.['@openmrs/esm-patient-chart-app']?.extensionSlots?.[submenuSlotKey];

if (submenuConfig && submenuConfig.add) {
const dashboardTitles = submenuConfig.add.map((dashboardKey) => {
return submenuConfig.configure?.[dashboardKey]?.title || 'Unnamed Dashboard';
});

return dashboardTitles.length ? dashboardTitles.join(', ') : 'No dashboards available';
}

return 'No dashboards available';
};

const tableRows = results.map((contentPackage) => {
const clinicalViewName = getNavGroupTitle(contentPackage);
const dashboardTitles = getDashboardTitles(contentPackage);

return {
id: contentPackage.key,
name: (
<ConfigurableLink
className={styles.link}
to={`${window.spaBase}/clinical-views-builder/edit/${contentPackage.id}`}
templateParams={{ clinicalViewUuid: contentPackage.id }}
>
{clinicalViewName}
</ConfigurableLink>
),
dashboards: dashboardTitles,
actions: (
<ActionButtons
responsiveSize={responsiveSize}
clinicalViewKey={contentPackage.key}
t={t}
onEdit={() => handleEdit(contentPackage.key)}
onDelete={() => handleDelete(contentPackage.key)}
onDownload={() => handleDownload(contentPackage.key)}
/>
),
};
});

const handleSearch = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -158,6 +237,7 @@ function ContentPackagesList({ contentPackages, isValidating, mutate, t }: any)
<span>{isValidating ? <InlineLoading /> : null}</span>
</div>
</div>
<div className={styles.tableHeading}>{t('clinicalViewsTableHeader', 'Clinical Views List')}</div>
<DataTable rows={tableRows} headers={tableHeaders} size={isTablet ? 'lg' : 'sm'} useZebraStyles>
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
<>
Expand All @@ -182,7 +262,7 @@ function ContentPackagesList({ contentPackages, isValidating, mutate, t }: any)
})
}
>
{t('createNewClinicalview', 'Create a new clinical view')}
{t('createNewClinicalView', 'Create a new clinical view')}
</Button>
</TableToolbarContent>
</TableToolbar>
Expand Down Expand Up @@ -224,7 +304,7 @@ function ContentPackagesList({ contentPackages, isValidating, mutate, t }: any)
{paginated && (
<ContentPackagesBuilderPagination
currentItems={results.length}
totalItems={searchResults.length}
totalItems={filteredViews.length}
onPageNumberChange={({ page }) => {
goTo(page);
}}
Expand Down
26 changes: 9 additions & 17 deletions src/components/dashboard/dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -106,29 +106,21 @@

}

.tableHeading {
display: flex;
align-items: center;
@include type.type-style('heading-compact-02');
min-height: 2.5rem;
width: 100%;
padding: 1rem 0;
margin: 2rem 0;
}
.content {
@include type.type-style('heading-compact-02');
color: colors.$gray-70;
margin-bottom: 0.5rem;
}

.tileContainer {
background-color: colors.$white-0;
border-top: 1px solid colors.$gray-70;
padding: 5rem 0;
}

.tile {
margin: auto;
width: fit-content;
}

.tileContent {
display: flex;
flex-direction: column;
align-items: center;
}

.warningMessage {
margin: 1rem 0;
}
Expand Down
Loading
Loading