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

High Level Claims Summary Statistics #553

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { DatePicker, DatePickerInput } from '@carbon/react';
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './summary-header.scss';

interface ClaimsSummaryHeaderProps {
filters: {
fromDate: Date | null;
toDate: Date | null;
};
onFilterChanged: (updateFn: (currentFilters: any) => any) => void;
statusOptions?: string[];
}

const ClaimSummaryHeader: React.FC<ClaimsSummaryHeaderProps> = ({ filters, onFilterChanged }) => {
const { t } = useTranslation();

const today = useMemo(() => new Date(), []);
const sixMonthsAgo = useMemo(() => {
const date = new Date(today);
date.setMonth(today.getMonth() - 6);
return date;
}, [today]);

useEffect(() => {
if (!filters.fromDate && !filters.toDate) {
onFilterChanged(() => ({
fromDate: sixMonthsAgo,
toDate: today,
}));
}
}, [filters, onFilterChanged, sixMonthsAgo, today]);

const handleDateChange = ([fromDate, toDate]: [Date, Date]) => {
onFilterChanged(() => ({
fromDate,
toDate,
}));
};

return (
<div className={styles.summaryContainer}>
<DatePicker
datePickerType="range"
value={[filters.fromDate, filters.toDate]}
onChange={handleDateChange}
aria-label={t('datePicker.rangeLabel', 'Select date range')}>
<DatePickerInput
id="date-picker-input-id-start"
placeholder={t('datePicker.startPlaceholder', 'mm/dd/yyyy')}
size="md"
labelText={t('datePicker.startLabel', 'Start Date')}
/>
<DatePickerInput
id="date-picker-input-id-finish"
placeholder={t('datePicker.endPlaceholder', 'mm/dd/yyyy')}
size="md"
labelText={t('datePicker.endLabel', 'End Date')}
/>
</DatePicker>
</div>
);
};

export default ClaimSummaryHeader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@use '@carbon/layout';
@use '@carbon/type';
@use '@carbon/colors';

.summaryContainer {
display: flex;
margin-top: layout.$spacing-05;
gap: layout.$spacing-01;
}

.input {
min-width: 300px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React, { useEffect, useState } from 'react';
import { BarChartOptions, GroupedBarChart, ScaleTypes } from '@carbon/charts-react';
import useClaimsAggregate from '../../../hooks/useClaimsAggregate';

interface MetricData {
summaryGraph: { group: string; month: string; value: number }[];
}

const ClaimsSummaryChart = () => {
const [metrics, setMetrics] = useState<MetricData>({ summaryGraph: [] });

const { isLoading, summarizedData, error } = useClaimsAggregate();

useEffect(() => {
if (summarizedData?.length) {
const transformedData = summarizedData.flatMap((item) => [
{ group: 'Claimed', month: item.month, value: item.claimedTotal },
{ group: 'Approved', month: item.month, value: item.approvedTotal },
]);
setMetrics({ summaryGraph: transformedData });
}
}, [summarizedData]);

if (isLoading) {
return <div>Loading data...</div>;
}

if (error) {
return <div>Error loading claims data: {error.message}</div>;
}

const options: BarChartOptions = {
title: 'Analysis of Claimed vs Approved Amount by Month',
legend: {
enabled: true,
},
axes: {
left: {
mapsTo: 'month',
title: 'Month',
scaleType: ScaleTypes.LABELS,
},
bottom: {
mapsTo: 'value',
title: 'Amount (Ksh)',
scaleType: ScaleTypes.LINEAR,
includeZero: true,
},
},
height: '400px',
};

return (
<div style={{ padding: '2rem' }} aria-label="Claims Summary Chart">
<GroupedBarChart data={metrics.summaryGraph} options={options} />
</div>
);
};

export default ClaimsSummaryChart;
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import styles from '../../metrics/metrics.scss';
import { ClaimsManagementHeader } from '../header/claims-header.component';
import ClaimsSummaryHeader from '../header/summary-header.component';
import { ClaimsSummaryFilter } from '../../../types';
import ClaimsSummaryChart from './claims-summary-chart.component';

import MetricsCard from '../../metrics/metrics-card.component';
import { convertToCurrency } from '../../../helpers';
import useClaimsAggregate from '../../../hooks/useClaimsAggregate';

const MainMetrics = () => {
const [filters, setFilters] = useState({
fromDate: null,
toDate: null,
});
const onFilterChanged = (updateFn: (currentFilters: any) => any) => {
setFilters(updateFn(filters));
};

const t = (key, fallback) => fallback;

const { isLoading, summarizedData, error } = useClaimsAggregate();

if (error) {
return <div>Error loading claims data</div>;
}

if (isLoading) {
return <div>Loading claims data...</div>;
}

const totalClaimed = summarizedData.reduce((sum, item) => sum + item.claimedTotal, 0);
const totalApproved = summarizedData.reduce((sum, item) => sum + item.approvedTotal, 0);
const totalPending = totalClaimed - totalApproved;

const preApps = 0;
const preAppsApproved = 0;
const preAppsPending = 0;

return (
<div className={`omrs-main-content`}>
<ClaimsManagementHeader title={t('claims', 'Claims Summary')} />
<ClaimsSummaryHeader filters={filters} onFilterChanged={onFilterChanged} />
<>
<div className={styles.cardContainer} data-testid="claims-metrics">
<MetricsCard
label={t('ksh', '')}
value={convertToCurrency(totalClaimed)}
headerLabel={t('claimsItems', 'Total Claimed')}
/>
<MetricsCard
label={t('ksh', '')}
value={convertToCurrency(totalApproved)}
headerLabel={t('claimsItems', 'Total Approved')}
/>
<MetricsCard
label={t('ksh', '')}
value={convertToCurrency(totalPending)}
headerLabel={t('claimsItems', 'Amount Pending')}
/>
</div>
<div className={styles.cardContainer} data-testid="claims-metrics">
<MetricsCard label={t('ksh', '')} value={preApps} headerLabel={t('claimsItems', 'Total Preauth')} />
<MetricsCard
label={t('ksh', '')}
value={preAppsApproved}
headerLabel={t('claimsItems', 'Approved Preauth')}
/>
<MetricsCard label={t('ksh', '')} value={preAppsPending} headerLabel={t('claimsItems', 'Pending Preauth')} />
</div>
<ClaimsSummaryChart />
</>
</div>
);
};

export default MainMetrics;
66 changes: 66 additions & 0 deletions packages/esm-billing-app/src/hooks/useClaimsAggregate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMemo } from 'react';
import { openmrsFetch, restBaseUrl } from '@openmrs/esm-framework';
import useSWR from 'swr';

interface Claim {
uuid: string;
claimCode: string;
dateFrom: string | null;
dateTo: string | null;
claimedTotal: number;
approvedTotal: number;
status: string;
externalId?: string;
}

interface ClaimResponse {
results: Claim[];
}

const useClaimsAggregate = () => {
const { data, error, isValidating } = useSWR<ClaimResponse>(
`${restBaseUrl}/claim?v=custom:(uuid,claimCode,dateFrom,dateTo,claimedTotal,approvedTotal,status,externalId)`,
async (url) => {
const response = await openmrsFetch<ClaimResponse>(url);
return response.data;
},
);

const summarizedData = useMemo(() => {
if (!data || !data.results) {
return [];
}

const summary = data.results.reduce((acc, item) => {
const date = item.dateFrom || item.dateTo;
const month = new Date(date ?? '').toLocaleString('default', {
month: 'long',
year: 'numeric',
});

if (!acc[month]) {
acc[month] = { claimedTotal: 0, approvedTotal: 0 };
}

acc[month].claimedTotal += item.claimedTotal || 0;
acc[month].approvedTotal += item.approvedTotal || 0;

return acc;
}, {} as Record<string, { claimedTotal: number; approvedTotal: number }>);

return Object.entries(summary).map(([month, totals], index) => ({
id: `${index + 1}`,
month,
claimedTotal: totals.claimedTotal,
approvedTotal: totals.approvedTotal,
}));
}, [data]);

return {
isLoading: isValidating,
error,
summarizedData,
};
};

export default useClaimsAggregate;
7 changes: 7 additions & 0 deletions packages/esm-billing-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ export const claimsManagementSideNavGroup = getSyncLifecycle(
}),
options,
);
export const claimsSummaryOverviewDashboardLink = getSyncLifecycle(
createLeftPanelLink({
name: 'claims-summary',
title: 'Claims Summary',
}),
options,
);
export const claimsManagementOverviewDashboardLink = getSyncLifecycle(
createLeftPanelLink({
name: 'claims-overview',
Expand Down
2 changes: 2 additions & 0 deletions packages/esm-billing-app/src/root.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import BillManager from './billable-services/bill-manager/bill-manager.component
import { ChargeItemsDashboard } from './billable-services/dashboard/dashboard.component';
import { PaymentHistory } from './billable-services/payment-history/payment-history.component';
import { BillingDashboard } from './billing-dashboard/billing-dashboard.component';
import ClaimsManagementSummary from './claims/claims-management/main/claims-summary-main.component';
import ClaimsManagementOverview from './claims/claims-management/main/claims-overview-main.component';
import ClaimsManagementPreAuthRequest from './claims/claims-management/main/claims-pre-auth-main.component';
import ClaimScreen from './claims/dashboard/claims-dashboard.component';
Expand All @@ -21,6 +22,7 @@ const RootComponent: React.FC = () => {
<BrowserRouter basename={baseName}>
<Routes>
<Route path="/" element={<BillingDashboard />} />
<Route path="/claims-summary" element={<ClaimsManagementSummary />} />
<Route path="/claims-overview" element={<ClaimsManagementOverview />} />
<Route path="/preauth-requests" element={<ClaimsManagementPreAuthRequest />} />
<Route
Expand Down
7 changes: 7 additions & 0 deletions packages/esm-billing-app/src/routes.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,18 @@
},
"featureFlag": "healthInformationExchange"
},

{
"component": "claimsSummaryOverviewDashboardLink",
"name": "claims-management-summary-link",
"slot": "claims-management-dashboard-link-slot"
},
{
"component": "claimsManagementOverviewDashboardLink",
"name": "claims-management-overview-link",
"slot": "claims-management-dashboard-link-slot"
},

{
"component": "preAuthRequestsDashboardLink",
"name": "preauthrequest-overview-link",
Expand Down
6 changes: 6 additions & 0 deletions packages/esm-billing-app/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,12 @@ export interface ClaimsPreAuthFilter {
search?: string;
}

export interface ClaimsSummaryFilter {
fromDate: Date;
toDate: Date;
status: string;
}

export interface BenefitDataResponse {
title?: string;
allocation?: string;
Expand Down
Loading