Skip to content
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
58 changes: 55 additions & 3 deletions src/components/PricelistsFilters.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,14 @@ import {
GRID_RESPONSIVE_STANDARD,
GRID_RESPONSIVE_SMALL,
} from "@openimis/fe-core";
import { FormControlLabel, Grid, Checkbox } from "@mui/material";
import { FormControlLabel, Grid, Checkbox, FormHelperText } from "@mui/material";
import { styled } from "@mui/material/styles";
import {
buildLocationFilter,
shouldDisableRegion,
getRegionHelperText,
validateLocationFilters
} from "../utils/filtersUtils";

const StyledPricelistsFilter = styled('section')(({ theme }) => ({
padding: "0 0 10px 0",
Expand All @@ -25,21 +31,50 @@ const PricelistsFilter = (props) => {
const { filters, onChangeFilters, modulesManager } = props;
const { formatMessage } = useTranslations("medical_pricelist", modulesManager);

/**
* Handle the change of region with priority logic
* If a district is selected, the region is ignored
*/
const onRegionChange = (value) => {
// If a district is already selected, do not allow the region change
if (shouldDisableRegion(filters)) {
return;
}

onChangeFilters([
{ id: "region", value, filter: value ? `location_Uuid: "${value.uuid}"` : null },
{ id: "district", value: null, filter: null },
{ id: "district", value: null, filter: null }, // Reset the district
]);
};

/**
* Handle the change of district with absolute priority
* The district always overrides the region
*/
const onDistrictChange = (value) => {
onChangeFilters([{ id: "district", value, filter: value ? `location_Uuid: "${value.uuid}"` : null }]);
const updates = [
{ id: "district", value, filter: value ? `location_Uuid: "${value.uuid}"` : null }
];

// If a district is selected, reset the region
if (value && value.uuid) {
updates.push({ id: "region", value: null, filter: null });
}

onChangeFilters(updates);
};

const onNameChange = (value) => {
onChangeFilters([{ id: "name", value, filter: `name_Icontains: "${value}"` }]);
};

const triggerDebounceName = useDebounceCb(onNameChange, modulesManager.getConf("fe-admin", "debounceTime", 500));

// UX states
const isRegionDisabled = shouldDisableRegion(filters);
const regionHelperText = getRegionHelperText(filters);
const locationValidation = validateLocationFilters(filters);

return (
<StyledPricelistsFilter>
<Grid container>
Expand Down Expand Up @@ -68,7 +103,13 @@ const PricelistsFilter = (props) => {
value={filters?.region?.value}
withNull={true}
onChange={onRegionChange}
disabled={isRegionDisabled}
/>
{regionHelperText && (
<FormHelperText style={{ marginTop: 4 }}>
{regionHelperText}
</FormHelperText>
)}
</Grid>
}
/>
Expand All @@ -85,6 +126,11 @@ const PricelistsFilter = (props) => {
key={filters?.region?.value}
onChange={onDistrictChange}
/>
{isRegionDisabled && (
<FormHelperText style={{ marginTop: 4, color: '#666' }}>
Prioritaire sur la région
</FormHelperText>
)}
</Grid>
}
/>
Expand Down Expand Up @@ -138,6 +184,12 @@ const PricelistsFilter = (props) => {
}
/>
</Grid>
{/* Display validation error if necessary */}
{!locationValidation.isValid && (
<Grid item xs={12} style={{ color: 'red', marginTop: 8 }}>
{locationValidation.error}
</Grid>
)}
</StyledPricelistsFilter>
);
};
Expand Down
20 changes: 5 additions & 15 deletions src/components/PricelistsSearcher.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const DeleteIcon = GetIconComponent("Delete")

import { combine, useTranslations, ConfirmDialog, Searcher, withModulesManager } from "@openimis/fe-core";
import PricelistsFilters from "./PricelistsFilters";
import { buildServicesPricelistFilters, buildPaginationParams } from "../utils/filtersUtils";

const isRowDisabled = (_, row) => Boolean(row.validityTo);
const isRowLocked = () => false;
Expand Down Expand Up @@ -89,21 +90,10 @@ const PricelistsSearcher = (props) => {
}, []);

const filtersToQueryParams = useCallback((state) => {
const params = Object.keys(state.filters)
.filter((contrib) => !!state.filters[contrib].filter)
.map((contrib) => state.filters[contrib].filter);
if (!state.beforeCursor && !state.afterCursor) {
params.push(`first: ${state.pageSize}`);
}
if (state.afterCursor) {
params.push(`after: "${state.afterCursor}"`);
params.push(`first: ${state.pageSize}`);
}
if (state.beforeCursor) {
params.push(`before: "${state.beforeCursor}"`);
params.push(`last: ${state.pageSize}`);
}
return params;
const locationFilters = buildServicesPricelistFilters(state);
const paginationParams = buildPaginationParams(state);

return [...locationFilters, ...paginationParams];
}, []);

return (
Expand Down
115 changes: 115 additions & 0 deletions src/utils/filtersUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Utilities for building GraphQL filters for medical price lists
*
* Problem solved: Avoid duplicate location_Uuid in GraphQL queries
* Business rule: District > Region (district is more precise and prioritary)
*/

/**
* Build the filters GraphQL for price lists respecting the district > region priority
* @param {Object} state - Current filters state
* @returns {Array} - Array of validated GraphQL filters
*/
export const buildServicesPricelistFilters = (state) => {
if (!state || !state.filters) {
return [];
}

const { filters } = state;
const result = [];

const locationFilter = buildLocationFilter(filters);
if (locationFilter) {
result.push(locationFilter);
}

Object.keys(filters).forEach(key => {
if (key !== 'region' && key !== 'district' && filters[key]?.filter) {
result.push(filters[key].filter);
}
});

return result;
};

/**
* Build the location filter respecting the district > region priority
* @param {Object} filters - Object of filters
* @returns {string|null} - GraphQL filter for location_Uuid or null
*/
export const buildLocationFilter = (filters) => {
if (filters.district?.value?.uuid) {
return `location_Uuid: "${filters.district.value.uuid}"`;
}

if (filters.region?.value?.uuid) {
return `location_Uuid: "${filters.region.value.uuid}"`;
}

return null;
};

/**
* Determine if the region should be disabled based on the selected district
* @param {Object} filters - Current filters state
* @returns {boolean} - true if the region should be disabled
*/
export const shouldDisableRegion = (filters) => {
return !!(filters.district?.value?.uuid);
};

/**
* Generate the helper text for the region field
* @param {Object} filters - Current filters state
* @returns {string} - Helper text explanation
*/
export const getRegionHelperText = (filters) => {
if (filters.district?.value?.uuid) {
return `Region automatically determined by district: ${filters.district.value.name || filters.district.value.code}`;
}
return '';
};

/**
* Build the pagination parameters for GraphQL
* @param {Object} state - Pagination state
* @returns {Array} - Pagination parameters for GraphQL
*/
export const buildPaginationParams = (state) => {
const params = [];

if (!state.beforeCursor && !state.afterCursor) {
params.push(`first: ${state.pageSize}`);
}

if (state.afterCursor) {
params.push(`after: "${state.afterCursor}"`);
params.push(`first: ${state.pageSize}`);
}

if (state.beforeCursor) {
params.push(`before: "${state.beforeCursor}"`);
params.push(`last: ${state.pageSize}`);
}

return params;
};

/**
* Validate that filters do not contain location conflicts
* @param {Object} filters - Filters to validate
* @returns {Object} - { isValid: boolean, error: string|null }
*/
export const validateLocationFilters = (filters) => {
const hasRegion = !!(filters.region?.value?.uuid);
const hasDistrict = !!(filters.district?.value?.uuid);

if (hasRegion && hasDistrict) {
return {
isValid: false,
error: 'A district and a region cannot be selected simultaneously. The district takes priority.'
};
}

return { isValid: true, error: null };
};
Loading