diff --git a/packages/fhir-keycloak-user-management/src/components/UserList/UserDetails-v2/index.tsx b/packages/fhir-keycloak-user-management/src/components/UserList/UserDetails-v2/index.tsx
index 74ec92088..70d501244 100644
--- a/packages/fhir-keycloak-user-management/src/components/UserList/UserDetails-v2/index.tsx
+++ b/packages/fhir-keycloak-user-management/src/components/UserList/UserDetails-v2/index.tsx
@@ -1,16 +1,17 @@
import React from 'react';
import { CloseOutlined } from '@ant-design/icons';
-import { Alert, Button, Descriptions, Divider, Table, Tabs, Tag } from 'antd';
+import { Alert, Button, Col, Descriptions, Divider, Row, Table, Tabs, Tag } from 'antd';
+import { Tree, generateFhirLocationTree } from '@opensrp/fhir-location-management';
import { Organization } from '@opensrp/team-management';
import { Spin } from 'antd';
import { useTranslation } from '../../../mls'
import { useParams } from 'react-router';
-import { BrokenPage, Resource404, TableLayout, getResourcesFromBundle, parseFhirHumanName, useSimpleTabularView, useTabularViewWithLocalSearch } from '@opensrp/react-utils';
+import { BrokenPage, FHIRServiceClass, Resource404, TableLayout, getResourcesFromBundle, parseFhirHumanName, useSimpleTabularView, useTabularViewWithLocalSearch } from '@opensrp/react-utils';
import { PageHeader } from '@ant-design/pro-layout';
import { KEYCLOAK_URL_USERS, KEYCLOAK_URL_USER_GROUPS, KeycloakUser, URL_USER, UserGroupDucks } from '@opensrp/user-management';
-import { KeycloakService } from '@opensrp/keycloak-service';
+import { KeycloakAPIService, KeycloakService } from '@opensrp/keycloak-service';
import { useQuery } from 'react-query';
-import { groupResourceType, practitionerResourceType } from '../../../constants';
+import { groupResourceType, keycloakRoleMappingsEndpoint, practitionerResourceType } from '../../../constants';
import { IPractitionerRole } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IPractitionerRole';
import { IPractitioner } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IPractitioner';
import { CodeableConcept } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/codeableConcept';
@@ -19,6 +20,9 @@ import { ICareTeam } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/ICareTeam';
import { IOrganization } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IOrganization';
import { IGroup } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IGroup';
import { IBundle } from '@smile-cdr/fhirts/dist/FHIR-R4/interfaces/IBundle';
+import { Coding } from '@smile-cdr/fhirts/dist/FHIR-R4/classes/coding';
+import { Link } from 'react-router-dom';
+import { StringLocale } from 'yup';
/** New details view. ->
@@ -42,23 +46,108 @@ const useGetKeycloakUserRoles = (userId: string) => {
// roles assigned to user by clients.
}
-
-// TODO - what if we do not have a linked practitioner.
-const useGetPractitioner = (userId: string) => {
- // get practitioner include practitionerRoles. Multiple practitioners having multiple practitionerRoles.
- // some practitioenerRoles are purely for assignment. Add codes where possible.
+interface KeycloakGroupDetailsProp {
+ keycloakBaseUrl: string;
+ resourceId: string;
}
-const useGetCareTeams = () => {
- // get careTeams that reference this practitioner
+const KeycloakGroupDetails = (props: KeycloakGroupDetailsProp) => {
+ const { t } = useTranslation();
+ const { keycloakBaseUrl, resourceId } = props;
+
+ // TODO - useSimpleTabular view but for keycloak requests.
+ const { data, error, isLoading } = useQuery([], () => new KeycloakService(
+ `${KEYCLOAK_URL_USERS}/${resourceId}${KEYCLOAK_URL_USER_GROUPS}`,
+ keycloakBaseUrl
+ ).list())
+
+ if (error && !data) {
+ return ;
+ }
+
+ // name, path, leave action.
+ const columns = [
+ {
+ title: t('Name'),
+ dataIndex: 'name' as const,
+ },
+ {
+ title: t('Path'),
+ dataIndex: 'path' as const,
+ },
+ {
+ title: t(''),
+ dataIndex: 'id' as const,
+ render: (id: string) => ,
+ },
+ ];
+
+ const tableProps = {
+ datasource: data,
+ columns,
+ loading: isLoading,
+ // pagination: true,
+ };
+
+ return
}
-const useGetOrganization = () => {
- // get organizations that reference this practitioner either directly or via a careTeam
+interface RoleMapping {
+ id: string;
+ name: string;
+ description: string;
+ composite: boolean;
+ clientRole: boolean;
+ containerId: string;
+}
+interface ClientRoleMapping {
+ id: string;
+ client: string;
+ mappings: RoleMapping[]
}
+interface KeycloakUserRoleMappings {
+ realMappings?: RoleMapping[];
+ clientMappings?: Record
+}
+const KeycloakRoleDetails = (props: KeycloakGroupDetailsProp) => {
+ const { t } = useTranslation();
+ const { keycloakBaseUrl, resourceId } = props;
-const useGetLocations = () => {
- // get locations where this practitioner is assigned.
+ // TODO - useSimpleTabular view but for keycloak requests.
+ const { data, error, isLoading } = useQuery([], () => new KeycloakService(
+ `${KEYCLOAK_URL_USERS}/${resourceId}/${keycloakRoleMappingsEndpoint}`,
+ keycloakBaseUrl
+ ).list())
+
+ if (error && !data) {
+ return ;
+ }
+
+ // name, path, leave action.
+ const columns = [
+ {
+ title: t('Name'),
+ dataIndex: 'name' as const,
+ },
+ {
+ title: t('Path'),
+ dataIndex: 'path' as const,
+ },
+ {
+ title: t(''),
+ dataIndex: 'id' as const,
+ render: (id: string) => ,
+ },
+ ];
+
+ const tableProps = {
+ datasource: data,
+ columns,
+ loading: isLoading,
+ // pagination: true,
+ };
+
+ return
}
// - if user record not found then show 404 under page details.
@@ -81,8 +170,7 @@ const routes = [{
]
export const UserDetails = (props: UserDetailProps) => {
- const { keycloakBaseURL: keycloakBaseUrl, fhirBaseURL: fhirBBaseUrl } = props
- console.log({ props })
+ const { keycloakBaseURL: keycloakBaseUrl, fhirBaseURL: fhirBaseUrl } = props
const params = useParams<{ id: string }>()
const { id: resourceId } = params;
const { t } = useTranslation();
@@ -95,6 +183,23 @@ export const UserDetails = (props: UserDetailProps) => {
new KeycloakService(`${KEYCLOAK_URL_USERS}`, keycloakBaseUrl).read(resourceId)
);
+ const practitionerDetailsResourceType = "practitioner-details"
+
+ const extraQueryFilters = {
+ "keycloak-uuid": resourceId,
+ }
+
+ const {
+ data: practitionerDetails, isLoading: detailsLoading, error: detailsError,
+
+ } = useQuery([practitionerDetailsResourceType, resourceId], () => new FHIRServiceClass(fhirBaseUrl, practitionerDetailsResourceType).list(extraQueryFilters), {
+ select: (res) => {
+ // invariant : expect practitioner-details will always ever be a single record per keycloak user.
+ return getResourcesFromBundle(res)[0]
+ }
+ });
+ const practDetailsByResName: PractitionerDetail['fhir'] = practitionerDetails?.fhir ?? {}
+
if (userIsLoading) {
return
}
@@ -179,12 +284,12 @@ export const UserDetails = (props: UserDetailProps) => {
size={"middle"}
items={
[
- { label: "Groups", key: 'Groups', children: },
+ { label: "Groups", key: 'Groups', children: },
{ label: "Roles", key: 'Roles', children: },
- { label: "Practitioners", key: 'Practitioners', children: },
- { label: "CareTeams", key: 'CareTeams', children: },
- { label: "Organizations", key: 'Organizations', children: },
- { label: "Locations", key: 'Locations', children: },
+ { label: "Practitioners", key: 'Practitioners', children: },
+ { label: "CareTeams", key: 'CareTeams', children: },
+ { label: "Organizations", key: 'Organizations', children: },
+ { label: "Locations", key: 'Locations', children: },
]
}
/>
@@ -195,91 +300,25 @@ export const UserDetails = (props: UserDetailProps) => {
);
};
-// const UserDetailsTabView = ({keycloakBaseUrl, resourceId}: {keycloakBaseUrl: string, resourceId: string}) => {
-
-// // get groups in a searchable way
-// // read userGroup
-// const { data, isLoading, error, isFetching } = useQuery<
-// UserGroupDucks.KeycloakUserGroup[]
-// >([groupResourceType, resourceId], () => {
-// return new KeycloakService(
-// `${KEYCLOAK_URL_USERS}/${resourceId}${KEYCLOAK_URL_USER_GROUPS}`,
-// keycloakBaseUrl
-// ).list();
-// });
-
-// if (error && !data) {
-// return ;
-// }
-
-// const tableData = (data?.records ?? []).map((org: IGroup, index: number) => {
-// return {
-// ...parseGroup(org),
-// key: `${index}`,
-// };
-// });
-
-// const columns =
-
-// const tableProps = {
-// datasource: tableData,
-// columns,
-// loading: isFetching || isLoading,
-// pagination: tablePaginationProps,
-// };
-
-
-
-
-// }
-
interface PractitionerDetail extends Resource {
fhir: {
careteams?: ICareTeam[];
teams?: IOrganization[];
- locationHierarchyList: any[]; // TODO - import LocationHierarchy
- practitionerRoles: IPractitionerRole[];
- groups: IGroup[];
- practitioner: IPractitioner[]
+ locationHierarchyList?: any[]; // TODO - import LocationHierarchy
+ practitionerRoles?: IPractitionerRole[];
+ groups?: IGroup[];
+ practitioner?: IPractitioner[]
}
}
-function groupResourcesIdDetails(details: PractitionerDetail[]){
- const groupedData = {}
- for (const detail of details){
- for(const [_, resources] of Object.entries(detail.fhir)){
- for(const resource of resources){
-
- }
- }
- }
-}
-const DetailsTabView = ({ fhirBaseUrl, keycloakId }: { fhirBaseUrl: string, keycloakId: string }) => {
+const PractitionerDetailsView = ({ loading, practitionerDetails }: { loading: boolean, practitionerDetails: PractitionerDetail['fhir'] }) => {
const { t } = useTranslation();
- /** Get practitioner details and group them, provision them for the tab children, they can consume whichever data they want.
- */
- const practitionerDetailsResourceType = "practitioner-details"
-
- const extraQueryFilters = {
- "keycloak-uuid": keycloakId,
- }
-
- const {
- data, isFetching, isLoading, error,
-
- } = useQuery([practitionerDetailsResourceType, keycloakId], () => new KeycloakService(practitionerDetailsResourceType, fhirBaseUrl).list(extraQueryFilters), {
- select: (res) => {
- // invariant : expect practitioner-details will always ever be a single record per keycloak user.
- return getResourcesFromBundle(res)[0]
- }
- });
- const resourcesByName = data?.fhir ?? {}
-
- // loop through records, and for each practitioner figure their practitionerRoles.
- const tableData = processPractitionerDetails(data?.records ?? [])
+ const practitioners = practitionerDetails.practitioner ?? [];
+ const practitionerRoles = practitionerDetails.practitionerRoles ?? [];
+ const tableData = processPractitionerDetails(practitioners, practitionerRoles)
const columns = [
@@ -302,8 +341,8 @@ const DetailsTabView = ({ fhirBaseUrl, keycloakId }: { fhirBaseUrl: string, keyc
const tableProps = {
datasource: tableData,
columns,
- loading: isFetching || isLoading,
- pagination: tablePaginationProps,
+ loading,
+ // pagination: true,
};
return
@@ -311,41 +350,82 @@ const DetailsTabView = ({ fhirBaseUrl, keycloakId }: { fhirBaseUrl: string, keyc
}
-const PractitionerDetailsView = ({ fhirBaseUrl, keycloakId }: { fhirBaseUrl: string, keycloakId: string }) => {
+const CareTeamDetailsView = ({ loading, practitionerDetails }: { loading: boolean, practitionerDetails: PractitionerDetail['fhir'] }) => {
const { t } = useTranslation();
- /** get all practitioners linked to this user.
- * where do practitioner role information come in. revInclude practitionerRoles.
- */
- /** get practitioners that have the given secondary identfier, an include practitionerRoles that reference those practitioners. */
- const extraQueryFilters = {
- identifier: keycloakId,
- _revinclude: "PractitionerRole:practitioner"
- }
- const {
- queryValues: { data, isFetching, isLoading, error },
- tablePaginationProps,
- searchFormProps,
- } = useSimpleTabularView(fhirBaseUrl, practitionerResourceType, extraQueryFilters);
+ const careTeams = practitionerDetails.careteams ?? [];
+ const tableData = careTeams.map(resource => {
+ const { id, status, name, } = resource
+ return {
+ id, status, name
+ }
+ })
- // loop through records, and for each practitioner figure their practitionerRoles.
- const tableData = processPractitionerDetails(data?.records ?? [])
+
+ // identifier, status,
+ const columns = [
+ {
+ title: t('Id'),
+ dataIndex: 'id' as const,
+ },
+ {
+ title: t('Name'),
+ dataIndex: 'name' as const,
+ render: (name: string) => {name}
+ },
+ {
+ title: t('status'),
+ dataIndex: 'status' as const,
+ render: (code: Coding) => ,
+ },
+ ];
+
+ const tableProps = {
+ datasource: tableData,
+ columns,
+ loading,
+ // pagination: true,
+ };
+
+ return
+
+}
+
+
+const OrganizationDetailsView = ({ loading, practitionerDetails }: { loading: boolean, practitionerDetails: PractitionerDetail['fhir'] }) => {
+ const { t } = useTranslation();
+ // get organization Affiliation - use it tag the codings for the organizations.
+ const organizations = practitionerDetails.teams ?? [];
+ const tableData = organizations.map(resource => {
+ const { id, active, type, name } = resource
+ return {
+ id, active, type: type ?? [], name
+ }
+ })
+
+
+ // identifier, status,
const columns = [
+ {
+ title: t('Id'),
+ dataIndex: 'id' as const,
+ },
{
title: t('Name'),
- dataIndex: 'name',
+ dataIndex: 'name' as const,
+ render: (name: string) => {name}
},
{
title: t('Active'),
- dataIndex: 'active',
- render: (value: boolean) => (value === true ? 'Active' : 'Inactive'),
+ dataIndex: 'active' as const,
+ render: (isActive: string) => isActive ? "Active" : "Inactive",
},
{
- title: t('Coding'),
- dataIndex: 'concepts',
+ title: t('Type'),
+ dataIndex: 'type' as const,
render: (concepts: CodeableConcept[]) => concepts.map(concept => ),
},
];
@@ -353,42 +433,140 @@ const PractitionerDetailsView = ({ fhirBaseUrl, keycloakId }: { fhirBaseUrl: str
const tableProps = {
datasource: tableData,
columns,
- loading: isFetching || isLoading,
- pagination: tablePaginationProps,
+ loading,
+ // pagination: true,
};
return
}
-function processPractitionerDetails(records: (IPractitioner | IPractitionerRole)[]) {
+
+const LocationDetailsView = ({ loading, practitionerDetails }: { loading: boolean, practitionerDetails: PractitionerDetail['fhir'] }) => {
+ const { t } = useTranslation();
+
+
+ // get organization Affiliation - use it tag the codings for the organizations.
+ const hierarchies = practitionerDetails.locationHierarchyList ?? [];
+ const treeData = hierarchies.map(rawTree => generateFhirLocationTree(rawTree))
+ // const tableData = hierarchies.map(resource => {
+ // const tree =
+ // const {id, active, type,name } = resource
+ // return {
+ // id, active, type, name
+ // }
+ // })
+
+
+ // identifier, status,
+ const columns = [
+ {
+ title: t('Id'),
+ dataIndex: 'id' as const,
+ },
+ {
+ title: t('Name'),
+ dataIndex: 'name' as const,
+ render: (name: string) => {name}
+ },
+ {
+ title: t('Active'),
+ dataIndex: 'active' as const,
+ render: (isActive: string) => isActive ? "Active" : "Inactive",
+ },
+ {
+ title: t('Type'),
+ dataIndex: 'type' as const,
+ render: (concepts: CodeableConcept[]) => concepts.map(concept => ),
+ },
+ ];
+
+ // const tableProps = {
+ // datasource: tableData,
+ // columns,
+ // loading,
+ // // pagination: true,
+ // };
+
+ return <>
+
+
+ {
+ // dispatch(setSelectedNode(node));
+ }}
+ />
+
+ {/*
+
+
+ {selectedNode ? selectedNode.model.node.name : t('Location Unit')}
+
+
+
+
+
+
+
+
+
{
+ setDetailId(row.id);
+ }}
+ />
+
+ */}
+
+ >
+
+}
+
+
+
+function processPractitionerDetails(practitioners: IPractitioner[], practitionerRoles: IPractitionerRole[]) {
const tableData: Record = {}
const tempPractitionerRoleCodings: Record = {};
- for (const res of records) {
- if (res.resourceType === practitionerResourceType) {
- const typedRes = res as IPractitioner
- const resName = typedRes.name?.[0] // TODO - use get official name
- // add to store
- tableData[`${practitionerResourceType}/${typedRes.id}`] = { name: parseFhirHumanName(resName), active: res.active, concepts: [] }
+ for (const res of practitioners) {
+ const typedRes = res
+ const resName = typedRes.name?.[0] // TODO - use get official name
+ // add to store
+ tableData[`${practitionerResourceType}/${typedRes.id}`] = { name: parseFhirHumanName(resName), active: res.active, concepts: [] }
+ }
+
+ for (const res of practitionerRoles) {
+ // practitionerRole resource
+ const typedRes = res as IPractitionerRole
+ const practitionerId = typedRes.practitioner?.reference as string
+
+ // extract the coding
+ const concepts = (typedRes.code ?? [])
+
+ // have we encountered a corresponding practitioner for this role
+ if (tableData[practitionerId] === undefined) {
+ tableData[practitionerId].codings.push(concepts)
}
- else {
- // practitionerRole resource
- const typedRes = res as IPractitionerRole
- const practitionerId = typedRes.practitioner?.reference as string
-
- // extract the coding
- const concepts = (typedRes.code ?? [])
-
- // have we encountered a corresponding practitioner for this role
- if (tableData[practitionerId] === undefined) {
- tableData[practitionerId].codings.push(concepts)
- }
- else if (tempPractitionerRoleCodings[practitionerId] === undefined) {
- tempPractitionerRoleCodings[practitionerId] = []
- } else {
- tempPractitionerRoleCodings[practitionerId].push(concepts)
- }
+ else if (tempPractitionerRoleCodings[practitionerId] === undefined) {
+ tempPractitionerRoleCodings[practitionerId] = []
+ } else {
+ tempPractitionerRoleCodings[practitionerId].push(concepts)
}
}
@@ -409,8 +587,13 @@ export const FhirCodeableConcept = ({ concept }: { concept: CodeableConcept }) =
}
+export const FhirCoding = ({ coding }: { coding: Coding }) => {
+ return Coding
+}
+
-// TODO - extract css to own file.- make sure to scope it to current component.
\ No newline at end of file
+// TODO - extract css to own file.- make sure to scope it to current component.
+// TODO - include any other details added to practitionerDetails.
\ No newline at end of file
diff --git a/packages/fhir-keycloak-user-management/src/components/UserList/ViewDetails/index.tsx b/packages/fhir-keycloak-user-management/src/components/UserList/ViewDetails/index.tsx
index 00b666c99..95ca03884 100644
--- a/packages/fhir-keycloak-user-management/src/components/UserList/ViewDetails/index.tsx
+++ b/packages/fhir-keycloak-user-management/src/components/UserList/ViewDetails/index.tsx
@@ -70,7 +70,9 @@ export const ViewDetails = (props: ViewDetailsProps) => {
() => {
return new FHIRServiceClass(fhirBaseUrl, practitionerResourceType)
.list({ identifier: resourceId })
- .then((res: IBundle) => getResourcesFromBundle(res)[0]);
+
+ },{
+ select:(res: IBundle) => getResourcesFromBundle(res)[0]
}
);
diff --git a/packages/fhir-keycloak-user-management/src/constants.ts b/packages/fhir-keycloak-user-management/src/constants.ts
index 899adc30d..3beb00841 100644
--- a/packages/fhir-keycloak-user-management/src/constants.ts
+++ b/packages/fhir-keycloak-user-management/src/constants.ts
@@ -3,6 +3,7 @@ export const careTeamResourceType = 'CareTeam';
export const organizationResourceType = 'Organization';
export const groupResourceType = 'Group';
export const practitionerRoleResourceType = 'PractitionerRole';
+export const keycloakRoleMappingsEndpoint = 'role-mappings';
// keycloak endpoints strings
export const keycloakCountEndpoint = 'count';
diff --git a/packages/fhir-keycloak-user-management/src/index.tsx b/packages/fhir-keycloak-user-management/src/index.tsx
index 5c893c25f..cf5a32740 100644
--- a/packages/fhir-keycloak-user-management/src/index.tsx
+++ b/packages/fhir-keycloak-user-management/src/index.tsx
@@ -1,3 +1,5 @@
export * from './components/CreateEditUser';
export * from './components/UserList/ListView';
export * from './constants';
+import {UserDetails as UserDetailsV2} from './components/UserList/UserDetails-v2';
+export {UserDetailsV2};