diff --git a/src/constants.ts b/src/constants.ts index 7e17c80..03b913b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -19,6 +19,33 @@ export const fhirTableHeaders = [ }, ]; +export const syncTaskTypeTableHeaders = [ + { + id: "1", + key: "name", + header: "NAME", + accessor: "name", + }, + { + id: "2", + key: "url", + header: "URL", + accessor: "url", + }, + { + id: "3", + key: "dataType", + header: "DATA TYPE ID", + accessor: "dataType", + }, + { + id: "4", + key: "uuid", + header: "UUID", + accessor: "uuid", + }, +]; + export const profileTransactionsHeaders = [ { id: "1", @@ -105,3 +132,78 @@ export const caseBasedPrimaryResourceTypes: Array = [ label: "Cohort Type", }, ]; + +export const syncTaskTypeDataTypes = [ + { + id: "dataType1", + label: "java.lang.Boolean", + }, + { + id: "dataType1", + label: "java.lang.Character", + }, + { + id: "dataType1", + label: "java.lang.Float", + }, + { + id: "dataType1", + label: "java.lang.Integer", + }, + { + id: "dataType1", + label: "java.lang.String", + }, + { + id: "dataType1", + label: "java.lang.Boolean", + }, + { + id: "dataType1", + label: "org.openmrs.Concept", + }, + { + id: "dataType1", + label: "org.openmrs.Drug", + }, + { + id: "dataType1", + label: "org.openmrs.Encounter", + }, + { + id: "dataType1", + label: "org.openmrs.Order", + }, + { + id: "dataType1", + label: "org.openmrs.TestOrder", + }, + { + id: "dataType1", + label: "org.openmrs.Location", + }, + { + id: "dataType1", + label: "org.openmrs.Patient", + }, + { + id: "dataType1", + label: "org.openmrs.Person", + }, + { + id: "dataType1", + label: "org.openmrs.ProgramWorkflow", + }, + { + id: "dataType1", + label: "org.openmrs.Provider", + }, + { + id: "dataType1", + label: "org.openmrs.User", + }, + { + id: "dataType1", + label: "org.openmrs.util.AttributableDate", + }, +]; diff --git a/src/fhir/sync-task-types/sync-task-type/sync-task-type.component.tsx b/src/fhir/sync-task-types/sync-task-type/sync-task-type.component.tsx new file mode 100644 index 0000000..e8a28b0 --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-type/sync-task-type.component.tsx @@ -0,0 +1,170 @@ +import React, { useState } from "react"; +import { + Button, + Dropdown, + Form, + FormGroup, + Stack, + TextInput, +} from "@carbon/react"; +import { Edit, Save } from "@carbon/react/icons"; +import { useTranslation } from "react-i18next"; +import { syncTaskTypeDataTypes } from "../../../constants"; +import styles from "./sync-task-type.scss"; + +export const SyncTaskTypeRow = ({ rowData }) => { + const { t } = useTranslation(); + + const [isEditMode, setIsEditMode] = useState(false); + + const dataTypesList = syncTaskTypeDataTypes.map((type) => ({ + id: type.id, + label: type.label, + })); + + const normalizeString = (str) => str?.toLowerCase().replace(/\s+/g, ""); + + const findItemByLabel = (apiValue, items) => { + const normalizedApiValue = normalizeString(apiValue); + return items?.find( + (item) => normalizeString(item.label) === normalizedApiValue + ); + }; + + const [selectedDataTypeType, setSelectedDataTypeType] = useState(() => + findItemByLabel(rowData?.dataType, dataTypesList) + ); + + const handleEdit = () => { + setIsEditMode(true); + }; + + const handleSubmit = () => { + setIsEditMode(false); + }; + + const handleCancel = () => { + setIsEditMode(false); + }; + + return ( + <> +
+
+
+ + + + + + + + + + + + + + +
+
+
+
+ + + (item ? item.label : "")} + items={dataTypesList} + label="Select Data Type" + selectedItem={selectedDataTypeType} + onChange={(event) => + setSelectedDataTypeType(event.selectedItem) + } + disabled={!isEditMode} + /> + + + + + + + + +
+
+
+
+ {!isEditMode && ( + + )} + {isEditMode && ( + <> + + + + )} +
+ + ); +}; + +export default SyncTaskTypeRow; diff --git a/src/fhir/sync-task-types/sync-task-type/sync-task-type.scss b/src/fhir/sync-task-types/sync-task-type/sync-task-type.scss new file mode 100644 index 0000000..5e1221b --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-type/sync-task-type.scss @@ -0,0 +1,30 @@ +@use '@carbon/styles/scss/colors'; +@use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/spacing'; +.formContainer { + width: 100%; + display: flex; +} + +.form { + width: 50%; +} + +.formFirst { + margin-right: 2rem; +} + +.formRight { + margin-top: 0.3rem; +} + +.editButtonsContainer { + display: flex; + margin-top: 1rem; + justify-content: flex-end; + .actionButton { + svg { + margin-right: 0.5rem; + } + } +} \ No newline at end of file diff --git a/src/fhir/sync-task-types/sync-task-types-list.component.tsx b/src/fhir/sync-task-types/sync-task-types-list.component.tsx new file mode 100644 index 0000000..e6418c9 --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-types-list.component.tsx @@ -0,0 +1,202 @@ +import { + DataTable, + Pagination, + Table, + TableBody, + TableCell, + TableContainer, + TableExpandHeader, + TableExpandRow, + TableExpandedRow, + TableHead, + TableHeader, + TableRow, + TableToolbar, + TableToolbarContent, + TableToolbarSearch, + Tile, +} from "@carbon/react"; +import { + isDesktop, + useLayoutType, + usePagination, +} from "@openmrs/esm-framework"; +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import styles from "../../components/data-table/data-tables.scss"; +import SyncTaskTypeRow from "./sync-task-type/sync-task-type.component"; + +type FilterProps = { + rowIds: Array; + headers: any; + cellsById: any; + inputValue: string; + getCellId: (row, key) => string; +}; + +interface ListProps { + columns: any; + data: any; +} + +const SyncTaskTypeList: React.FC = ({ columns, data }) => { + const { t } = useTranslation(); + const layout = useLayoutType(); + const isTablet = useLayoutType() === "tablet"; + const responsiveSize = isTablet ? "lg" : "sm"; + const [allRows, setAllRows] = useState([]); + const pageSizes = [10, 20, 30, 40, 50]; + const [currentPageSize, setPageSize] = useState(10); + const { + goTo, + results: paginatedList, + currentPage, + } = usePagination(data, currentPageSize); + + const handleFilter = ({ + rowIds, + headers, + cellsById, + inputValue, + getCellId, + }: FilterProps): Array => { + const filterTerm = inputValue.toLowerCase(); + + return rowIds.filter((rowId) => { + const nameHeader = headers.find((header) => header.key === "name"); + if (!nameHeader) { + return true; + } + + const cellId = getCellId(rowId, nameHeader.key); + const nameValue = cellsById[cellId].value; + + if (typeof nameValue === "boolean") { + return false; + } + + return ("" + nameValue).toLowerCase().includes(filterTerm); + }); + }; + + useEffect(() => { + const rows: Array> = []; + + paginatedList.map((item: any, index) => { + return rows.push({ ...item, id: index++ }); + }); + setAllRows(rows); + }, [paginatedList, allRows]); + + return ( + + {({ + rows, + headers, + getRowProps, + getHeaderProps, + getTableProps, + onInputChange, + }) => ( +
+ +
+ + + + + +
+ + + + + {headers.map((header, i) => ( + + {header.header} + + ))} + + + + {rows.map((row, index) => { + return ( + + + {row.cells.map((cell) => ( + {cell.value} + ))} + + {row.isExpanded ? ( + + + + ) : ( + + )} + + ); + })} + +
+ {rows.length === 0 ? ( +
+ +
+

+ {t("No data", "No data to display")} +

+
+
+
+ ) : null} + { + if (pageSize !== currentPageSize) { + setPageSize(pageSize); + } + if (page !== currentPage) { + goTo(page); + } + }} + /> +
+
+ )} +
+ ); +}; + +export default SyncTaskTypeList; diff --git a/src/fhir/sync-task-types/sync-task-types.component.tsx b/src/fhir/sync-task-types/sync-task-types.component.tsx new file mode 100644 index 0000000..13054fe --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-types.component.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import Header from "../../components/header/header.component"; +import Illustration from "../fhir-illustration.component"; +import { Button } from "@carbon/react"; +import { Add } from "@carbon/react/icons"; +import styles from "./sync-task-types.scss"; +import { useGetSyncTaskTypes } from "./sync-task-types.resource"; +import { syncTaskTypeTableHeaders } from "../../constants"; +import SyncTaskTypeList from "./sync-task-types-list.component"; +import { EmptyStateComponent } from "../../components/empty-state/empty-state.component"; + +export const SyncTaskTypes = () => { + const { syncTaskTypes } = useGetSyncTaskTypes(); + return ( + <> +
} + title={`Sync Task Types`} + /> + +
+ +
+ + {syncTaskTypes.length > 0 ? ( +
+ +
+ ) : ( + + )} + + ); +}; + +export default SyncTaskTypes; diff --git a/src/fhir/sync-task-types/sync-task-types.resource.ts b/src/fhir/sync-task-types/sync-task-types.resource.ts new file mode 100644 index 0000000..c88fa82 --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-types.resource.ts @@ -0,0 +1,26 @@ +import useSWR from "swr"; +import { openmrsFetch, restBaseUrl } from "@openmrs/esm-framework"; + +export interface SyncTaskTypeResponse { + results: Result[]; +} + +export interface Result { + uuid: string; + name: string; + dataType: string; + url: string; +} + +export function useGetSyncTaskTypes() { + const apiUrl = `${restBaseUrl}/synctasktype?v=full`; + const { data, error, isLoading } = useSWR< + { data: SyncTaskTypeResponse }, + Error + >(apiUrl, openmrsFetch); + return { + syncTaskTypes: data ? data?.data?.results : [], + isError: error, + isLoading, + }; +} diff --git a/src/fhir/sync-task-types/sync-task-types.scss b/src/fhir/sync-task-types/sync-task-types.scss new file mode 100644 index 0000000..ce87d0a --- /dev/null +++ b/src/fhir/sync-task-types/sync-task-types.scss @@ -0,0 +1,12 @@ +@use '@carbon/styles/scss/colors'; +@use '@carbon/styles/scss/type'; +@use '@carbon/styles/scss/spacing'; +.taskTypeContainer { + padding: 0 1rem 0 1rem; + overflow: auto; +} + +.createIcon { + padding: 1.0rem; + margin-right: 60px; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 56581a2..98ea048 100644 --- a/src/index.ts +++ b/src/index.ts @@ -46,7 +46,15 @@ export const hieHomeLink = getSyncLifecycle( export const fhirProfileLink = getSyncLifecycle( createLeftPanelLink({ name: "fhir-exchange", - title: "FHIR Profiles", + title: "Sync FHIR Profiles", + }), + options +); + +export const syncTaskTypesLink = getSyncLifecycle( + createLeftPanelLink({ + name: "sync-task-types", + title: "Sync Task Types", }), options ); diff --git a/src/left-panel-link.component.tsx b/src/left-panel-link.component.tsx index 6d7397c..fd20ef3 100644 --- a/src/left-panel-link.component.tsx +++ b/src/left-panel-link.component.tsx @@ -6,6 +6,7 @@ import { ConfigurableLink } from "@openmrs/esm-framework"; export interface LinkConfig { name: string; title: string; + slot?: string; } function LinkExtension({ config }: { config: LinkConfig }) { diff --git a/src/routes.json b/src/routes.json index 29f8d76..e168501 100644 --- a/src/routes.json +++ b/src/routes.json @@ -19,12 +19,20 @@ { "component": "hieHomeLink", "name": "hie-home-link", - "slot": "health-exchange-left-panel-slot" + "slot": "health-exchange-left-panel-slot", + "order": 0 }, { "component": "fhirProfileLink", "name": "fhir-profile-link", - "slot": "health-exchange-left-panel-slot" + "slot": "health-exchange-left-panel-slot", + "order": 1 + }, + { + "component": "syncTaskTypesLink", + "name": "sync-task-types", + "slot": "health-exchange-left-panel-slot", + "order": 2 }, { "component": "registriesDashboard", @@ -36,6 +44,14 @@ "component": "VLSuppressionPrediction", "slot": "action-menu-chart-items-slot" }, + { + "name": "sync-task-types-component", + "component": "SyncTaskTypesComponent" + }, + { + "name": "sync-tasktype-row-details", + "component": "SyncTaskTypeRow" + }, { "name": "ai-predictions-workspace", "component": "VLSuppressionPredictionWorkspace" @@ -152,4 +168,4 @@ "slot": "health-exchange-left-panel-slot" } ] -} +} \ No newline at end of file