From 7c975560708e88b903613319db9ebb3e9a6d3b6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20G=C3=A9r=C3=B4me?= Date: Mon, 9 Aug 2021 17:31:46 +0200 Subject: [PATCH 1/8] chore(deps): Upgrade some @rollup deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3c94934..6546430 100644 --- a/package.json +++ b/package.json @@ -26,10 +26,10 @@ "@babel/preset-react": "^7.9.4", "@babel/runtime": "^7.9.6", "@rollup/plugin-babel": "^5.0.0", - "@rollup/plugin-commonjs": "^11.1.0", + "@rollup/plugin-commonjs": "^20.0.0", "@rollup/plugin-json": "^4.0.3", - "@rollup/plugin-node-resolve": "^7.1.3", - "@rollup/plugin-url": "^5.0.0", + "@rollup/plugin-node-resolve": "^13.0.4", + "@rollup/plugin-url": "^6.1.0", "moment": "^2.25.3", "prop-types": "^15.7.2", "react-autosuggest": "^10.0.2", From edb6352838435db875265d3a027ed09d27ceb3d9 Mon Sep 17 00:00:00 2001 From: Eric Darchis Date: Mon, 20 Sep 2021 15:38:54 +0200 Subject: [PATCH 2/8] v1.3.0-rc1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6546430..857e6cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openimis/fe-medical_pricelist", - "version": "1.2.0-rc4", + "version": "1.3.0-rc1", "license": "AGPL-3.0-only", "description": "openIMIS Frontend Medical Price List reference module", "repository": "openimis/openimis-fe-medical_pricelist_js", From 03a76bb43ef8be7e35478b431ba88a08f8530394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20G=C3=A9r=C3=B4me?= Date: Wed, 18 Aug 2021 17:13:52 +0200 Subject: [PATCH 3/8] feat(Pricelists): Move pricelists management to the new frontend --- README.md | 2 +- package.json | 1 + src/actions.js | 192 +++++++++++++++++++- src/components/PriceOverruleDialog.js | 52 ++++++ src/components/PricelistDetailsPanel.js | 173 ++++++++++++++++++ src/components/PricelistForm.js | 40 +++++ src/components/PricelistGeneralPanel.js | 75 ++++++++ src/components/PricelistsFilters.js | 147 ++++++++++++++++ src/components/PricelistsSearcher.js | 145 ++++++++++++++++ src/constants.js | 12 ++ src/index.js | 32 +++- src/pages/ItemsPricelistDetailsPage.js | 131 ++++++++++++++ src/pages/ItemsPricelistsPage.js | 75 ++++++++ src/pages/ServicesPricelistDetailsPage.js | 131 ++++++++++++++ src/pages/ServicesPricelistsPage.js | 73 ++++++++ src/pickers/ItemsPriceListPicker.js | 8 +- src/pickers/PriceListPicker.js | 6 +- src/pickers/ServicesPriceListPicker.js | 10 +- src/reducer.js | 203 +++++++++++++++++++++- src/translations/en.json | 38 +++- 20 files changed, 1523 insertions(+), 23 deletions(-) create mode 100644 src/components/PriceOverruleDialog.js create mode 100644 src/components/PricelistDetailsPanel.js create mode 100644 src/components/PricelistForm.js create mode 100644 src/components/PricelistGeneralPanel.js create mode 100644 src/components/PricelistsFilters.js create mode 100644 src/components/PricelistsSearcher.js create mode 100644 src/constants.js create mode 100644 src/pages/ItemsPricelistDetailsPage.js create mode 100644 src/pages/ItemsPricelistsPage.js create mode 100644 src/pages/ServicesPricelistDetailsPage.js create mode 100644 src/pages/ServicesPricelistsPage.js diff --git a/README.md b/README.md index 6eb2d83..d5acc3f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ None ## Other Modules Listened Redux Actions -- `CLAIM_EDIT_HEALTH_FACILITY_SET`: triggering the load of the price lists (items and services) assiciated to a health facility +- `CLAIM_EDIT_HEALTH_FACILITY_SET`: triggering the load of the price lists (items and services) associated to a health facility ## Configurations Options diff --git a/package.json b/package.json index 857e6cb..3ca4981 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "@rollup/plugin-node-resolve": "^13.0.4", "@rollup/plugin-url": "^6.1.0", "moment": "^2.25.3", + "prettier": "^2.3.2", "prop-types": "^15.7.2", "react-autosuggest": "^10.0.2", "react-router-dom": "^5.2.0", diff --git a/src/actions.js b/src/actions.js index 4fdf5fa..89d68f5 100644 --- a/src/actions.js +++ b/src/actions.js @@ -1,11 +1,20 @@ -import { graphql, formatQuery, formatPageQuery, decodeId } from "@openimis/fe-core"; +import { + graphql, + formatQuery, + formatNodeQuery, + formatPageQueryWithCount, + formatPageQuery, + decodeId, + graphqlMutation, +} from "@openimis/fe-core"; +import { ITEMS_PRICELIST_TYPE, SERVICES_PRICELIST_TYPE } from "./constants"; export function fetchPriceLists(servicesPricelist, itemsPricelist) { let filters = []; let projections = []; if (!servicesPricelist && !itemsPricelist) { // nothing to do - return (dispatch) => {}; + return () => {}; } if (!!servicesPricelist) { filters.push(`servicesPricelistId: ${decodeId(servicesPricelist.id)}`); @@ -22,6 +31,27 @@ export function fetchPriceLists(servicesPricelist, itemsPricelist) { }); } +const PRICELIST_BY_ID_PROJECTIONS = [ + "id", + "uuid", + "name", + "validityFrom", + "validityTo", + "pricelistDate", + "detailsCount:details{totalCount}", + "location{id,name,uuid,code,type,parent{id,uuid,code,name,type}}", +]; + +export function fetchServicesPricelistById(mm, pricelistId) { + const query = formatNodeQuery("ServicesPricelistGQLType", pricelistId, PRICELIST_BY_ID_PROJECTIONS); + return graphql(query, "MEDICAL_PRICELIST_PRICELIST", { pricelistType: SERVICES_PRICELIST_TYPE }); +} + +export function fetchItemsPricelistById(mm, pricelistId) { + const query = formatNodeQuery("ItemsPricelistGQLType", pricelistId, PRICELIST_BY_ID_PROJECTIONS); + return graphql(query, "MEDICAL_PRICELIST_PRICELIST", { pricelistType: ITEMS_PRICELIST_TYPE }); +} + export function fetchServicesPriceLists(location) { let filters = null; if (!!location) { @@ -41,3 +71,161 @@ export function fetchItemsPriceLists(location) { let payload = formatPageQuery("itemsPricelists", filters, projections); return graphql(payload, "MEDICAL_LOCATION_ITEMS_PRICELIST", { location }); } + +export function fetchItemsPricelistsSummaries(_, filters = []) { + return fetchPricelistsSummaries(_, filters, ITEMS_PRICELIST_TYPE); +} + +export function fetchServicesPricelistsSummaries(_, filters = []) { + return fetchPricelistsSummaries(_, filters, SERVICES_PRICELIST_TYPE); +} + +function fetchPricelistsSummaries(_, filters = [], pricelistType) { + const projections = [ + "id", + "uuid", + "name", + "validityFrom", + "validityTo", + "pricelistDate", + "location{id,name,uuid,code,type,parent{id,uuid,code,name,type}}", + ]; + const nodeName = pricelistType === ITEMS_PRICELIST_TYPE ? "itemsPricelists" : "servicesPricelists"; + const payload = formatPageQueryWithCount(nodeName, filters, projections); + return graphql(payload, "MEDICAL_PRICELIST_SUMMARIES", { pricelistType }); +} + +export function fetchItemsPricelistDetails(_, filters = [], pricelistId) { + const projections = [ + "id", + "uuid", + "code", + "name", + "type", + "price", + pricelistId && `pricelistDetails(itemsPricelist: "${pricelistId}") {edges {node {priceOverrule}}}`, + ].filter(Boolean); + const query = formatPageQueryWithCount("medicalItems", filters, projections); + return graphql(query, "MEDICAL_PRICELIST_ITEMS"); +} + +export function fetchServicesPricelistDetails(_, filters = [], pricelistId) { + const projections = [ + "id", + "uuid", + "code", + "name", + "type", + "price", + pricelistId && `pricelistDetails(servicesPricelist: "${pricelistId}") {edges {node {priceOverrule}}}`, + ].filter(Boolean); + const query = formatPageQueryWithCount("medicalServices", filters, projections); + return graphql(query, "MEDICAL_PRICELIST_SERVICES"); +} + +function prepareInput(pricelist) { + return { + uuid: pricelist.uuid, + name: pricelist.name, + pricelistDate: pricelist.pricelistDate, + locationId: pricelist.location?.uuid, + addedDetails: pricelist.addedDetails, + removedDetails: pricelist.removedDetails, + priceOverrules: pricelist.priceOverrules + ? Object.entries(pricelist.priceOverrules).map(([uuid, price]) => ({ uuid, price })) + : undefined, + }; +} + +export function createServicesPricelist(mm, pricelist, clientMutationLabel) { + const input = prepareInput(pricelist); + input.clientMutationLabel = clientMutationLabel; + + return graphqlMutation( + ` + mutation ($input: CreateServicesPricelistMutationInput!) { + createServicesPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input } + ); +} +export function createItemsPricelist(mm, pricelist, clientMutationLabel) { + const input = prepareInput(pricelist); + input.clientMutationLabel = clientMutationLabel; + + return graphqlMutation( + ` + mutation ($input: CreateItemsPricelistMutationInput!) { + createItemsPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input } + ); +} + +export function updateServicesPricelist(mm, pricelist, clientMutationLabel) { + const input = prepareInput(pricelist); + input.clientMutationLabel = clientMutationLabel; + + return graphqlMutation( + ` + mutation ($input: UpdateServicesPricelistMutationInput!) { + updateServicesPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input } + ); +} +export function updateItemsPricelist(mm, pricelist, clientMutationLabel) { + const input = prepareInput(pricelist); + input.clientMutationLabel = clientMutationLabel; + + return graphqlMutation( + ` + mutation ($input: UpdateItemsPricelistMutationInput!) { + updateItemsPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input } + ); +} + +export function deleteServicesPricelist(mm, uuid, clientMutationLabel) { + return graphqlMutation( + ` + mutation ($input: DeleteServicesPricelistMutationInput!) { + deleteServicesPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input: { uuids: [uuid], clientMutationLabel } } + ); +} +export function deleteItemsPricelist(mm, uuid, clientMutationLabel) { + return graphqlMutation( + ` + mutation ($input: DeleteItemsPricelistMutationInput!) { + deleteItemsPricelist(input: $input) { + internalId + clientMutationId + } + } + `, + { input: { uuids: [uuid], clientMutationLabel } } + ); +} diff --git a/src/components/PriceOverruleDialog.js b/src/components/PriceOverruleDialog.js new file mode 100644 index 0000000..de6b5f4 --- /dev/null +++ b/src/components/PriceOverruleDialog.js @@ -0,0 +1,52 @@ +import React, { useState } from "react"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle } from "@material-ui/core"; +import { combine, FormattedMessage, NumberInput } from "@openimis/fe-core"; + +const styles = (theme) => ({ + primaryButton: theme.dialog.primaryButton, + secondaryButton: theme.dialog.secondaryButton, +}); + +const PriceOverruleDialog = (props) => { + const { classes, open, onCancel, defaultPrice, onConfirm } = props; + const [value, setValue] = useState(defaultPrice); + return ( + + + + + + + + + + + + + + + + + ); +}; + +const enhance = combine(withTheme, withStyles(styles)); + +export default enhance(PriceOverruleDialog); diff --git a/src/components/PricelistDetailsPanel.js b/src/components/PricelistDetailsPanel.js new file mode 100644 index 0000000..bad16d1 --- /dev/null +++ b/src/components/PricelistDetailsPanel.js @@ -0,0 +1,173 @@ +import React, { useState, useEffect } from "react"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { Table, withModulesManager, combine, useTranslations, ErrorBoundary } from "@openimis/fe-core"; +import { Paper, Grid, Typography, Checkbox, Button } from "@material-ui/core"; +import PriceOverruleDialog from "./PriceOverruleDialog"; + +const styles = (theme) => ({ + paper: theme.paper.paper, + item: theme.paper.item, + tableTitle: theme.table.title, + checkbox: { + padding: theme.spacing(0), + }, + editDetailBtn: { + padding: 0, + }, +}); + +const HEADERS = [ + "", + "medical_pricelist.table.code", + "medical_pricelist.table.name", + "medical_pricelist.table.type", + "medical_pricelist.table.price", + "medical_pricelist.table.overrule", + "", +]; + +const isItemActive = (edited, item) => { + return edited.addedDetails?.includes(item.uuid) || (item.isActive && !edited.removedDetails?.includes(item.uuid)); +}; + +const PricelistDetailsPanel = (props) => { + const { + classes, + modulesManager, + pageSize = 20, + edited, + edited_id, + readOnly, + details, + fetchDetails, + onEditedChanged, + } = props; + const { formatMessage } = useTranslations("medical_pricelist", modulesManager); + const [pagination, setPagination] = useState({ page: 0, afterCursor: null, beforeCursor: null }); + const [editedDetail, setEditedDetail] = useState(null); + useEffect(() => { + const filters = []; + if (pagination.afterCursor) { + filters.push(`first: ${pageSize}`, `after: "${pagination.afterCursor}"`); + } else if (pagination.beforeCursor) { + filters.push(`last: ${pageSize}`, `before: "${pagination.beforeCursor}"`); + } else { + filters.push(`first: ${pageSize}`); + } + + fetchDetails(filters); + }, [pagination.page, edited_id]); + + const onDetailChange = (event, item) => { + if (event.target.checked) { + onEditedChanged({ + ...edited, + // It's useless to add the item to the list of added items if it is already marked as active + addedDetails: !item.isActive ? (edited.addedDetails ?? []).concat(item.uuid) : edited.addedDetails, + removedDetails: edited.removedDetails && edited.removedDetails.filter((x) => x !== item.uuid), + }); + } else { + onEditedChanged({ + ...edited, + addedDetails: edited.addedDetails && edited.addedDetails.filter((x) => x !== item.uuid), + removedDetails: item.isActive ? (edited.removedDetails ?? []).concat([item.uuid]) : edited.removedDetails, + }); + } + }; + + const onPriceChange = (price) => { + editedDetail.priceOverrule = price; + onEditedChanged({ + ...edited, + priceOverrules: { + ...edited.priceOverrules, + [editedDetail.uuid]: price, + }, + }); + setEditedDetail(null); + }; + + return ( + <> + {editedDetail && ( + + setEditedDetail(null)} + /> + + )} + + + + + {formatMessage("pricelistForm.table.title")} + + + + + ( + onDetailChange(event, s)} + checked={isItemActive(edited, s)} + /> + ), + (s) => s.code, + (s) => s.name, + (s) => formatMessage(`medical_pricelist.table.type.${s.type.toLowerCase()}`), + (s) => s.price, + (s) => s.priceOverrule, + (s) => + isItemActive(edited, s) && + !readOnly && ( + + ), + ]} + aligns={HEADERS.map((_, i) => (i === HEADERS.length - 1 ? "right" : null))} + items={details.items} + withPagination + page={pagination.page} + onChangePage={(_, page) => + setPagination({ + afterCursor: page > pagination.page ? details.pageInfo.endCursor : null, // We'll load the next page + beforeCursor: page < pagination.page ? details.pageInfo.startCursor : null, // We'll load the previous page + page, + }) + } + count={details.pageInfo.totalCount} + rowsPerPage={pageSize} + rowsPerPageOptions={[pageSize]} + >
+
+
+
+
+ + ); +}; + +const enhance = combine(withModulesManager, withTheme, withStyles(styles)); + +export default enhance(PricelistDetailsPanel); diff --git a/src/components/PricelistForm.js b/src/components/PricelistForm.js new file mode 100644 index 0000000..3ee6699 --- /dev/null +++ b/src/components/PricelistForm.js @@ -0,0 +1,40 @@ +import React from "react"; +import { combine, withHistory, withModulesManager, Form } from "@openimis/fe-core"; +import PricelistGeneralPanel from "./PricelistGeneralPanel"; +import PricelistDetailsPanel from "./PricelistDetailsPanel"; +import ReplayIcon from "@material-ui/icons/Replay"; + +const PricelistForm = (props) => { + const { readOnly, onBack, onSave, onReset, pricelist, onChange, fetchDetails, details } = props; + const canSave = () => pricelist.name && pricelist.location && pricelist.pricelistDate; + return ( + <> +
, + onlyIfDirty: !readOnly, + }, + ]} + /> + + ); +}; + +const enhance = combine(withHistory, withModulesManager); +export default enhance(PricelistForm); diff --git a/src/components/PricelistGeneralPanel.js b/src/components/PricelistGeneralPanel.js new file mode 100644 index 0000000..bac988d --- /dev/null +++ b/src/components/PricelistGeneralPanel.js @@ -0,0 +1,75 @@ +import React from "react"; +import { withStyles, withTheme } from "@material-ui/core/styles"; + +import { Grid } from "@material-ui/core"; +import { FormPanel, withHistory, combine, withModulesManager, TextInput, PublishedComponent } from "@openimis/fe-core"; + +const styles = (theme) => ({ + item: theme.paper.item, +}); + +class PricelistGeneralPanel extends FormPanel { + onRegionChange = (value) => { + this.updateAttribute("location", value); + }; + onDistrictChange = (value) => { + this.updateAttribute("location", value ?? this.props.edited.location?.parent); + }; + + render() { + const { classes, readOnly, edited } = this.props; + const region = edited.location?.parent ?? edited.location; + const district = edited.location?.parent ? edited.location : null; + return ( + <> + + + this.updateAttribute("name", v)} + required + readOnly={readOnly} + value={edited?.name ?? ""} + /> + + + + + + + + + this.updateAttribute("pricelistDate", v)} + /> + + + + ); + } +} + +const enhance = combine(withHistory, withModulesManager, withTheme, withStyles(styles)); + +export default enhance(PricelistGeneralPanel); diff --git a/src/components/PricelistsFilters.js b/src/components/PricelistsFilters.js new file mode 100644 index 0000000..e465899 --- /dev/null +++ b/src/components/PricelistsFilters.js @@ -0,0 +1,147 @@ +import React from "react"; +import { + combine, + ControlledField, + PublishedComponent, + TextInput, + useTranslations, + withModulesManager, + useDebounceCb, +} from "@openimis/fe-core"; +import { FormControlLabel, Grid, Checkbox } from "@material-ui/core"; +import { withTheme, withStyles } from "@material-ui/core/styles"; + +const styles = (theme) => ({ + form: { + padding: "0 0 10px 0", + width: "100%", + }, + item: { + padding: theme.spacing(1), + }, +}); + +const PricelistsFilter = (props) => { + const { classes, filters, onChangeFilters, modulesManager } = props; + const { formatMessage } = useTranslations("medical_pricelist", modulesManager); + + const onRegionChange = (value) => { + onChangeFilters([ + { id: "region", value, filter: value ? `location_Uuid: "${value.uuid}"` : null }, + { id: "district", value: null, filter: null }, + ]); + }; + const onDistrictChange = (value) => { + onChangeFilters([{ id: "district", value, filter: value ? `location_Uuid: "${value.uuid}"` : null }]); + }; + const onNameChange = (value) => { + onChangeFilters([{ id: "name", value, filter: `name_Icontains: "${value}"` }]); + }; + + const triggerDebounceName = useDebounceCb(onNameChange, modulesManager.getConf("fe-admin", "debounceTime", 500)); + + return ( +
+ + + + + } + /> + + + + } + /> + + + + } + /> + + + onChangeFilters([ + { + id: "date", + value: d, + filter: d ? `pricelistDate: "${d}"` : null, + }, + ]) + } + /> + + } + /> + + + onChangeFilters([ + { + id: "showHistory", + value: !filters?.showHistory?.value, + filter: `showHistory: ${!filters?.showHistory?.value}`, + }, + ]) + } + /> + } + label={formatMessage("medical_pricelist.showHistory")} + /> + + } + /> + +
+ ); +}; + +const enhance = combine(withTheme, withStyles(styles), withModulesManager); + +export default enhance(PricelistsFilter); diff --git a/src/components/PricelistsSearcher.js b/src/components/PricelistsSearcher.js new file mode 100644 index 0000000..f6d8608 --- /dev/null +++ b/src/components/PricelistsSearcher.js @@ -0,0 +1,145 @@ +import React, { useCallback, useState } from "react"; +import { Tooltip, IconButton } from "@material-ui/core"; +import { Tab as TabIcon, Delete as DeleteIcon } from "@material-ui/icons"; +import { useTranslations, ConfirmDialog, Searcher, withModulesManager } from "@openimis/fe-core"; +import PricelistsFilters from "./PricelistsFilters"; +const isRowDisabled = (_, row) => Boolean(row.validityTo); +const isRowLocked = () => false; + +const getLocationByType = (location, type) => { + if (location.type === type) { + return location; + } else if (location.parent?.type === type) { + return location.parent; + } else { + return null; + } +}; + +const HEADERS = [ + "medical_pricelist.name", + "medical_pricelist.pricelist_date", + "medical_pricelist.region", + "medical_pricelist.district", + "medical_pricelist.valid_from", + "medical_pricelist.valid_to", + "", +]; +const ALIGNS = HEADERS.map((_, i) => i === HEADERS.length - 1 && "right"); + +const getAligns = () => ALIGNS; +const getHeaders = () => HEADERS; + +const PricelistsSearcher = (props) => { + const { + pageInfo, + items, + isFetching, + isFetched, + cacheFiltersKey, + canDelete, + onDelete, + modulesManager, + onDoubleClick, + onFiltersChange, + } = props; + const [confirmPricelistToDelete, setPricelistToDelete] = useState(null); + const [resetKey, setResetKey] = useState(); + const { formatMessage, formatMessageWithValues, formatDateFromISO } = useTranslations( + "medical_pricelist", + modulesManager + ); + + const onDeleteConfirm = (isConfirmed) => { + if (isConfirmed) { + onDelete(confirmPricelistToDelete); + setResetKey(Date.now()); + } + setPricelistToDelete(null); + }; + + const itemFormatters = useCallback( + () => [ + (pricelist) => pricelist.name, + (pricelist) => formatDateFromISO(pricelist.pricelistDate), + (pricelist) => + getLocationByType(pricelist.location, "R") + ? `${getLocationByType(pricelist.location, "R").code} - ${getLocationByType(pricelist.location, "R").name}` + : null, + (pricelist) => + getLocationByType(pricelist.location, "D") + ? `${getLocationByType(pricelist.location, "D").code} - ${getLocationByType(pricelist.location, "D").name}` + : null, + (pricelist) => formatDateFromISO(pricelist.validityFrom), + (pricelist) => formatDateFromISO(pricelist.validityTo), + (pricelist) => ( + <> + + onDoubleClick(pricelist, true)}> + + + + {canDelete(pricelist) && ( + + setPricelistToDelete(pricelist)}> + + + + )} + + ), + ], + [] + ); + const filtersToQueryParams = useCallback((state) => { + const params = Object.keys(state.filters) + .filter((contrib) => !!state.filters[contrib].filter) + .map((contrib) => state.filters[contrib].filter); + params.push(`first: ${state.pageSize}`); + if (state.afterCursor) { + params.push(`after: "${state.afterCursor}"`); + } + if (state.beforeCursor) { + params.push(`before: "${state.beforeCursor}"`); + } + return params; + }, []); + return ( + <> + {confirmPricelistToDelete && ( + + )} + r.uuid} + filtersToQueryParams={filtersToQueryParams} + onDoubleClick={onDoubleClick} + /> + + ); +}; + +export default withModulesManager(PricelistsSearcher); diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..3e71bee --- /dev/null +++ b/src/constants.js @@ -0,0 +1,12 @@ +export const RIGHT_SERVICES_PRICELISTS = 121201; +export const RIGHT_SERVICES_PRICELISTS_ADD = 121202; +export const RIGHT_SERVICES_PRICELISTS_EDIT = 121203; +export const RIGHT_SERVICES_PRICELISTS_DELETE = 121204; +export const RIGHT_SERVICES_PRICELISTS_DUPLICATE = 121205; +export const RIGHT_ITEMS_PRICELISTS = 121301; +export const RIGHT_ITEMS_PRICELISTS_ADD = 121302; +export const RIGHT_ITEMS_PRICELISTS_EDIT = 121303; +export const RIGHT_ITEMS_PRICELISTS_DELETE = 121304; +export const RIGHT_ITEMS_PRICELISTS_DUPLICATE = 121305; +export const SERVICES_PRICELIST_TYPE = "services"; +export const ITEMS_PRICELIST_TYPE = "items"; diff --git a/src/index.js b/src/index.js index 950ed5e..d676ccd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,20 +1,42 @@ import messages_en from "./translations/en.json"; import reducer from "./reducer"; import PriceListLoader from "./components/PriceListLoader"; -import ServicesPriceListPicker from "./pickers/ServicesPriceListPicker"; -import ItemsPriceListPicker from "./pickers/ItemsPriceListPicker"; +import ServicesPricelistPicker from "./pickers/ServicesPricelistPicker"; +import ItemsPricelistPicker from "./pickers/ItemsPricelistPicker"; +import ServicesPricelistsPage from "./pages/ServicesPricelistsPage"; +import ItemsPricelistsPage from "./pages/ItemsPricelistsPage"; +import ServicesPricelistDetailsPage from "./pages/ServicesPricelistDetailsPage"; +import ItemsPricelistDetailsPage from "./pages/ItemsPricelistDetailsPage"; const DEFAULT_CONFIG = { "translations": [{ key: "en", messages: messages_en }], "reducers": [{ key: "medical_pricelist", reducer }], "refs": [ - { key: "medical_pricelist.ServicesPriceListPicker", ref: ServicesPriceListPicker }, + { key: "medical_pricelist.ServicesPriceListPicker", ref: ServicesPricelistPicker }, { key: "medical_pricelist.ServicesPriceListPicker.projection", ref: ["id"] }, - { key: "medical_pricelist.ItemsPriceListPicker", ref: ItemsPriceListPicker }, + { key: "medical_pricelist.ItemsPriceListPicker", ref: ItemsPricelistPicker }, { key: "medical_pricelist.ItemsPriceListPicker.projection", ref: ["id"] }, + + // Services Routes references + { key: "medical_pricelist.servicesPricelists", ref: "medical/pricelists/services" }, + { key: "medical_pricelist.servicesPricelistDetails", ref: "medical/pricelists/services" }, + { key: "medical_pricelist.newServicesPricelist", ref: "medical/pricelists/services/new" }, + + // Items Routes references + { key: "medical_pricelist.itemsPricelists", ref: "medical/pricelists/items" }, + { key: "medical_pricelist.itemsPricelistDetails", ref: "medical/pricelists/items" }, + { key: "medical_pricelist.newItemsPricelist", ref: "medical/pricelists/items/new" }, ], "core.Boot": [PriceListLoader], -} + "core.Router": [ + { path: "medical/pricelists/services", component: ServicesPricelistsPage }, + { path: "medical/pricelists/services/new", component: ServicesPricelistDetailsPage }, + { path: "medical/pricelists/services/:price_list_id", component: ServicesPricelistDetailsPage }, + { path: "medical/pricelists/items", component: ItemsPricelistsPage }, + { path: "medical/pricelists/items/new", component: ItemsPricelistDetailsPage }, + { path: "medical/pricelists/items/:price_list_id", component: ItemsPricelistDetailsPage }, + ], +}; export const MedicalPriceListModule = (cfg) => { return { ...DEFAULT_CONFIG, ...cfg }; diff --git a/src/pages/ItemsPricelistDetailsPage.js b/src/pages/ItemsPricelistDetailsPage.js new file mode 100644 index 0000000..677251a --- /dev/null +++ b/src/pages/ItemsPricelistDetailsPage.js @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; +import clsx from "clsx"; +import { bindActionCreators } from "redux"; +import { combine, withHistory, withModulesManager, historyPush, ProgressOrError } from "@openimis/fe-core"; +import { withStyles, withTheme } from "@material-ui/core/styles"; +import { ErrorBoundary, useTranslations } from "@openimis/fe-core"; +import PricelistForm from "../components/PricelistForm"; +import { + createItemsPricelist, + updateItemsPricelist, + fetchItemsPricelistById, + fetchItemsPricelistDetails, +} from "../actions"; +import { RIGHT_ITEMS_PRICELISTS_EDIT } from "../constants"; +const styles = (theme) => ({ + page: theme.page, + locked: theme.page.locked, +}); + +const ItemsPriceListDetailsPage = (props) => { + const { + classes, + isFetching, + error, + match, + history, + modulesManager, + rights, + updateItemsPricelist, + fetchItemsPricelistById, + fetchItemsPricelistDetails, + createItemsPricelist, + details, + } = props; + const { formatMessageWithValues } = useTranslations("medical_pricelist", modulesManager); + const [isLocked, setLocked] = useState(false); + const [resetKey, setResetKey] = useState(null); + const [pricelist, setPricelist] = useState({}); + + useEffect(() => { + if (match.params.price_list_id) { + fetchItemsPricelistById(modulesManager, match.params.price_list_id); + } else { + setPricelist({}); + } + }, [match.params.price_list_id, resetKey]); + + useEffect(() => { + if (props.pricelist) { + setPricelist(props.pricelist); + } + }, [props.pricelist]); + + const onSave = (pricelist) => { + setLocked(true); + if (pricelist.uuid) { + updateItemsPricelist( + modulesManager, + pricelist, + formatMessageWithValues("updatePricelist.mutationLabel", { name: pricelist.name }) + ); + } else { + createItemsPricelist( + modulesManager, + pricelist, + formatMessageWithValues("createPricelist.mutationLabel", { name: pricelist.name }) + ); + } + }; + + const onReset = () => { + setLocked(false); + setResetKey(Date.now()); + }; + + const fetchDetails = (filters) => { + fetchItemsPricelistDetails(modulesManager, filters, pricelist?.id); + }; + + return ( +
+ + + {!isFetching && ( + historyPush(modulesManager, history, "medical_pricelist.itemsPricelists")} + onSave={rights.includes(RIGHT_ITEMS_PRICELISTS_EDIT) ? onSave : undefined} + onReset={onReset} + details={details} + fetchDetails={fetchDetails} + /> + )} + +
+ ); +}; + +const mapStateToProps = (state, props) => ({ + rights: state.core?.user?.i_user?.rights ?? [], + pricelist: props.match.params.price_list_id + ? state.medical_pricelist.pricelists.items.items[props.match.params.price_list_id] + : null, + isFetching: state.medical_pricelist.pricelists.items.isFetching, + error: state.medical_pricelist.pricelists.items.error, + details: state.medical_pricelist.items, +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + createItemsPricelist, + updateItemsPricelist, + fetchItemsPricelistById, + fetchItemsPricelistDetails, + }, + dispatch + ); + +const enhance = combine( + withTheme, + withStyles(styles), + withHistory, + withModulesManager, + connect(mapStateToProps, mapDispatchToProps) +); +export default enhance(ItemsPriceListDetailsPage); diff --git a/src/pages/ItemsPricelistsPage.js b/src/pages/ItemsPricelistsPage.js new file mode 100644 index 0000000..0c94d8b --- /dev/null +++ b/src/pages/ItemsPricelistsPage.js @@ -0,0 +1,75 @@ +import React from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { Fab } from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; +import { withHistory, historyPush, combine, withModulesManager, useTranslations, withTooltip } from "@openimis/fe-core"; +import PricelistsSearcher from "../components/PricelistsSearcher"; +import { fetchItemsPricelistsSummaries, deleteItemsPricelist } from "../actions"; +import { RIGHT_ITEMS_PRICELISTS_DELETE, RIGHT_ITEMS_PRICELISTS_ADD } from "../constants"; + +const styles = (theme) => ({ + page: { + ...theme.page, + paddingInline: 16, + }, + fab: theme.fab, +}); + +const ItemsPricelistsPage = (props) => { + const { classes, modulesManager, history } = props; + const { formatMessage, formatMessageWithValues } = useTranslations("medical_pricelist", modulesManager); + const rights = useSelector((state) => state.core.user?.i_user?.rights ?? []); + const data = useSelector((state) => state.medical_pricelist.summaries.items); + const dispatch = useDispatch(); + const onDoubleClick = (row, newTab = false) => { + historyPush(modulesManager, history, "medical_pricelist.itemsPricelists", [row.id], newTab); + }; + + const onAdd = () => { + historyPush(modulesManager, history, "medical_pricelist.newItemsPricelist"); + }; + + const onFiltersChange = (filters) => { + dispatch(fetchItemsPricelistsSummaries(modulesManager, filters)); + }; + + const onDelete = (pricelist) => { + dispatch( + deleteItemsPricelist( + modulesManager, + pricelist.uuid, + formatMessageWithValues("deletePricelist.mutationLabel", { name: pricelist.name }) + ) + ); + }; + + return ( +
+ rights.includes(RIGHT_ITEMS_PRICELISTS_DELETE) && !pricelist.validTo} + onDoubleClick={onDoubleClick} + cacheFiltersKey="medicalItemsPriceListsPageFiltersCache" + /> + {rights.includes(RIGHT_ITEMS_PRICELISTS_ADD) && + withTooltip( +
+ + + +
, + formatMessage("addNewPriceListTooltip") + )} +
+ ); +}; + +const enhance = combine(withModulesManager, withHistory, withTheme, withStyles(styles)); + +export default enhance(ItemsPricelistsPage); diff --git a/src/pages/ServicesPricelistDetailsPage.js b/src/pages/ServicesPricelistDetailsPage.js new file mode 100644 index 0000000..76a1c75 --- /dev/null +++ b/src/pages/ServicesPricelistDetailsPage.js @@ -0,0 +1,131 @@ +import React, { useState, useEffect } from "react"; +import { connect } from "react-redux"; +import clsx from "clsx"; +import { bindActionCreators } from "redux"; +import { combine, withHistory, withModulesManager, historyPush, ProgressOrError } from "@openimis/fe-core"; +import { withStyles, withTheme } from "@material-ui/core/styles"; +import { ErrorBoundary, useTranslations } from "@openimis/fe-core"; +import PricelistForm from "../components/PricelistForm"; +import { + createServicesPricelist, + updateServicesPricelist, + fetchServicesPricelistById, + fetchServicesPricelistDetails, +} from "../actions"; +import { RIGHT_SERVICES_PRICELISTS_EDIT } from "../constants"; +const styles = (theme) => ({ + page: theme.page, + locked: theme.page.locked, +}); + +const ServicesPriceListDetailsPage = (props) => { + const { + classes, + isFetching, + error, + match, + history, + modulesManager, + rights, + updateServicesPricelist, + fetchServicesPricelistDetails, + fetchServicesPricelistById, + createServicesPricelist, + details, + } = props; + const { formatMessageWithValues } = useTranslations("medical_pricelist", modulesManager); + const [isLocked, setLocked] = useState(false); + const [resetKey, setResetKey] = useState(null); + const [pricelist, setPricelist] = useState({}); + + useEffect(() => { + if (match.params.price_list_id) { + fetchServicesPricelistById(modulesManager, match.params.price_list_id); + } else { + setPricelist({}); + } + }, [match.params.price_list_id, resetKey]); + + useEffect(() => { + if (props.pricelist) { + setPricelist(props.pricelist); + } + }, [props.pricelist]); + + const onSave = (pricelist) => { + setLocked(true); + if (pricelist.uuid) { + updateServicesPricelist( + modulesManager, + pricelist, + formatMessageWithValues("updatePricelist.mutationLabel", { name: pricelist.name }) + ); + } else { + createServicesPricelist( + modulesManager, + pricelist, + formatMessageWithValues("createPricelist.mutationLabel", { name: pricelist.name }) + ); + } + }; + + const onReset = () => { + setLocked(false); + setResetKey(Date.now()); + }; + + const fetchDetails = (filters) => { + fetchServicesPricelistDetails(modulesManager, filters, pricelist?.id); + }; + + return ( +
+ + + {!isFetching && ( + historyPush(modulesManager, history, "medical_pricelist.servicesPricelists")} + onSave={rights.includes(RIGHT_SERVICES_PRICELISTS_EDIT) ? onSave : undefined} + onReset={onReset} + details={details} + fetchDetails={fetchDetails} + /> + )} + +
+ ); +}; + +const mapStateToProps = (state, props) => ({ + rights: state.core?.user?.i_user?.rights ?? [], + pricelist: props.match.params.price_list_id + ? state.medical_pricelist.pricelists.services.items[props.match.params.price_list_id] + : null, + isFetching: state.medical_pricelist.pricelists.services.isFetching, + error: state.medical_pricelist.pricelists.services.error, + details: state.medical_pricelist.services, +}); + +const mapDispatchToProps = (dispatch) => + bindActionCreators( + { + createServicesPricelist, + updateServicesPricelist, + fetchServicesPricelistById, + fetchServicesPricelistDetails, + }, + dispatch + ); + +const enhance = combine( + withTheme, + withStyles(styles), + withHistory, + withModulesManager, + connect(mapStateToProps, mapDispatchToProps) +); +export default enhance(ServicesPriceListDetailsPage); diff --git a/src/pages/ServicesPricelistsPage.js b/src/pages/ServicesPricelistsPage.js new file mode 100644 index 0000000..818167b --- /dev/null +++ b/src/pages/ServicesPricelistsPage.js @@ -0,0 +1,73 @@ +import React from "react"; +import { useSelector, useDispatch } from "react-redux"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { Fab } from "@material-ui/core"; +import AddIcon from "@material-ui/icons/Add"; +import { withHistory, historyPush, combine, withModulesManager, useTranslations, withTooltip } from "@openimis/fe-core"; +import PricelistsSearcher from "../components/PricelistsSearcher"; +import { fetchServicesPricelistsSummaries, deleteServicesPricelist } from "../actions"; +import { RIGHT_SERVICES_PRICELISTS_DELETE, RIGHT_SERVICES_PRICELISTS_ADD } from "../constants"; + +const styles = (theme) => ({ + page: theme.page, + fab: theme.fab, +}); + +const ServicesPricelistsPage = (props) => { + const { classes, modulesManager, history } = props; + const { formatMessage, formatMessageWithValues } = useTranslations("medical_pricelist", modulesManager); + const rights = useSelector((state) => state.core.user?.i_user?.rights ?? []); + const data = useSelector((state) => state.medical_pricelist.summaries.services); + const dispatch = useDispatch(); + + const onDoubleClick = (row, newTab = false) => { + historyPush(modulesManager, history, "medical_pricelist.servicesPricelistDetails", [row.id], newTab); + }; + + const onAdd = () => { + historyPush(modulesManager, history, "medical_pricelist.newServicesPricelist"); + }; + + const onFiltersChange = (filters) => { + dispatch(fetchServicesPricelistsSummaries(modulesManager, filters)); + }; + + const onDelete = (pricelist) => { + dispatch( + deleteServicesPricelist( + modulesManager, + pricelist.uuid, + formatMessageWithValues("deletePricelist.mutationLabel", { name: pricelist.name }) + ) + ); + }; + + return ( +
+ rights.includes(RIGHT_SERVICES_PRICELISTS_DELETE) && !pricelist.validTo} + onDoubleClick={onDoubleClick} + cacheFiltersKey="medicalServicesPriceListsPageFiltersCache" + /> + {rights.includes(RIGHT_SERVICES_PRICELISTS_ADD) && + withTooltip( +
+ + + +
, + formatMessage("addNewPriceListTooltip") + )} +
+ ); +}; + +const enhance = combine(withModulesManager, withHistory, withTheme, withStyles(styles)); + +export default enhance(ServicesPricelistsPage); diff --git a/src/pickers/ItemsPriceListPicker.js b/src/pickers/ItemsPriceListPicker.js index 3765925..5527e0c 100644 --- a/src/pickers/ItemsPriceListPicker.js +++ b/src/pickers/ItemsPriceListPicker.js @@ -3,13 +3,13 @@ import { connect } from "react-redux"; import { bindActionCreators } from "redux"; import { injectIntl } from "react-intl"; import { fetchItemsPriceLists } from "../actions"; -import PriceListPicker from "./PriceListPicker"; +import PricelistPicker from "./PricelistPicker"; -class ItemsPriceListPicker extends Component { +class ItemsPricelistPicker extends Component { render() { const { name, value, onChange, readOnly, region, district } = this.props; return ( - { return bindActionCreators({ fetchItemsPriceLists }, dispatch); }; -export default injectIntl(connect(null, mapDispatchToProps)(ItemsPriceListPicker)); +export default injectIntl(connect(null, mapDispatchToProps)(ItemsPricelistPicker)); diff --git a/src/pickers/PriceListPicker.js b/src/pickers/PriceListPicker.js index 66e0a38..5c5e5a6 100644 --- a/src/pickers/PriceListPicker.js +++ b/src/pickers/PriceListPicker.js @@ -1,9 +1,9 @@ import React, { Component } from "react"; import { injectIntl } from "react-intl"; - +import _ from "lodash"; import { parseData, formatMessage, SelectInput, ProgressOrError } from "@openimis/fe-core"; -class PriceListPicker extends Component { +class PricelistPicker extends Component { state = { loading: true, baseOptions: [], @@ -98,4 +98,4 @@ class PriceListPicker extends Component { } } -export default injectIntl(PriceListPicker); +export default injectIntl(PricelistPicker); diff --git a/src/pickers/ServicesPriceListPicker.js b/src/pickers/ServicesPriceListPicker.js index 725fe82..d8cbe1d 100644 --- a/src/pickers/ServicesPriceListPicker.js +++ b/src/pickers/ServicesPriceListPicker.js @@ -1,14 +1,14 @@ -import React, { Component } from "react"; +import React from "react"; import { connect } from "react-redux"; import { bindActionCreators } from "redux"; import { injectIntl } from "react-intl"; import { fetchServicesPriceLists } from "../actions"; -import PriceListPicker from "./PriceListPicker"; +import PricelistPicker from "./PricelistPicker"; -const ServicesPriceListPicker = (props) => { +const ServicesPricelistPicker = (props) => { const { fetchServicesPriceLists, name, value, onChange, readOnly, region, district } = props; return ( - { return bindActionCreators({ fetchServicesPriceLists }, dispatch); }; -export default injectIntl(connect(null, mapDispatchToProps)(ServicesPriceListPicker)); +export default injectIntl(connect(null, mapDispatchToProps)(ServicesPricelistPicker)); diff --git a/src/reducer.js b/src/reducer.js index 73208b3..9fd3ab8 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -1,4 +1,5 @@ -import { formatServerError, formatGraphQLError } from "@openimis/fe-core"; +import { formatServerError, formatGraphQLError, parseData, pageInfo } from "@openimis/fe-core"; +import { SERVICES_PRICELIST_TYPE } from "./constants"; function arrayToMap(arr) { return arr.reduce((map, a) => { @@ -15,10 +16,210 @@ function reducer( servicesPricelists: {}, itemsPricelists: {}, errorPricelist: null, + summaries: { + items: { + isFetching: false, + isFetched: false, + pageInfo: { totalCount: 0 }, + items: [], + error: null, + }, + services: { + isFetching: false, + isFetched: false, + pageInfo: { totalCount: 0 }, + items: [], + error: null, + }, + }, + pricelists: { + services: { + isFetching: false, + items: {}, + error: null, + }, + items: { + isFetching: false, + items: {}, + error: null, + }, + }, + items: { + isFetching: false, + error: null, + pageInfo: { totalCount: 0 }, + items: [], + }, + services: { + isFetching: false, + error: null, + pageInfo: { totalCount: 0 }, + items: [], + }, }, action ) { switch (action.type) { + case "MEDICAL_PRICELIST_SERVICES_REQ": + return { + ...state, + services: { + ...state.services, + isFetching: true, + error: null, + }, + }; + case "MEDICAL_PRICELIST_SERVICES_RESP": + const formatService = (service) => { + const isActive = service.pricelistDetails?.edges.length > 0 ?? false; + const d = { + ...service, + isActive, + priceOverrule: isActive ? service.pricelistDetails.edges[0].node.priceOverrule : undefined, + }; + delete d.pricelistDetails; + return d; + }; + return { + ...state, + services: { + ...state.services, + isFetching: false, + items: parseData(action.payload.data.medicalServices).map(formatService), + pageInfo: pageInfo(action.payload.data.medicalServices), + }, + }; + case "MEDICAL_PRICELIST_SERVICES_ERR": + return { + ...state, + services: { + ...state.services, + isFetching: false, + error: formatServerError(action.payload), + }, + }; + + case "MEDICAL_PRICELIST_ITEMS_REQ": + return { + ...state, + services: { + ...state.services, + isFetching: true, + error: null, + }, + }; + case "MEDICAL_PRICELIST_ITEMS_RESP": + const formatItem = (item) => { + const isActive = item.pricelistDetails?.edges.length > 0 ?? false; + const d = { + ...item, + isActive, + priceOverrule: isActive ? item.pricelistDetails.edges[0].node.priceOverrule : undefined, + }; + delete d.pricelistDetails; + return d; + }; + return { + ...state, + items: { + ...state.items, + isFetching: false, + items: parseData(action.payload.data.medicalItems).map(formatItem), + pageInfo: pageInfo(action.payload.data.medicalItems), + }, + }; + case "MEDICAL_PRICELIST_ITEMS_ERR": + return { + ...state, + items: { + ...state.items, + isFetching: false, + error: formatServerError(action.payload), + }, + }; + + case "MEDICAL_PRICELIST_PRICELIST_REQ": + return { + ...state, + pricelists: { + ...state.pricelists, + [action.meta.pricelistType]: { + ...state.pricelists[action.meta.pricelistType], + isFetching: true, + error: null, + }, + }, + }; + case "MEDICAL_PRICELIST_PRICELIST_RESP": + console.log(action); + return { + ...state, + pricelists: { + ...state.pricelists, + [action.meta.pricelistType]: { + ...state.pricelists[action.meta.pricelistType], + isFetching: false, + items: { + ...state.pricelists[action.meta.pricelistType].items, + [action.payload.data.node.id]: action.payload.data.node, + }, + }, + }, + }; + case "MEDICAL_PRICELIST_PRICELIST_ERR": + return { + ...state, + pricelists: { + ...state.pricelists, + [action.meta.pricelistType]: { + ...state.pricelists[action.meta.pricelistType], + isFetching: false, + error: formatServerError(action.payload), + }, + }, + }; + + case "MEDICAL_PRICELIST_SUMMARIES_REQ": + return { + ...state, + summaries: { + ...state.summaries, + [action.meta.pricelistType]: { + ...state.summaries[action.meta.pricelistType], + isFetching: true, + error: null, + }, + }, + }; + case "MEDICAL_PRICELIST_SUMMARIES_RESP": + const payloadField = + action.meta.pricelistType === SERVICES_PRICELIST_TYPE ? "servicesPricelists" : "itemsPricelists"; + return { + ...state, + summaries: { + ...state.summaries, + [action.meta.pricelistType]: { + ...state.summaries[action.meta.pricelistType], + isFetching: false, + isFetched: true, + items: parseData(action.payload.data[payloadField]), + pageInfo: pageInfo(action.payload.data[payloadField]), + }, + }, + }; + + case "MEDICAL_PRICELIST_SUMMARIES_ERR": + return { + ...state, + summaries: { + ...state.summaries, + [action.meta.pricelistType]: { + ...state.summaries[action.meta.pricelistType], + isFetching: false, + error: formatServerError(action.payload), + }, + }, + }; case "CLAIM_EDIT_HEALTH_FACILITY_SET": return { ...state, diff --git a/src/translations/en.json b/src/translations/en.json index 7642161..0b030c1 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1,4 +1,38 @@ { - "medical_pricelist.servicesPricelist": "Services Price List", - "medical_pricelist.itemsPricelist": "Items Price List" + "medical_pricelist.servicesPricelist": "Services Pricelist", + "medical_pricelist.itemsPricelist": "Items Pricelist", + "medical_pricelist.pricelistsSearcher.table.title": "{count} Pricelists", + "medical_pricelist.name": "Name", + "medical_pricelist.pricelist_date": "Date", + "medical_pricelist.region": "Region", + "medical_pricelist.district": "District", + "medical_pricelist.showHistory": "Show Historical Values", + "medical_pricelist.valid_from": "Valid From", + "medical_pricelist.valid_to": "Valid To", + "medical_pricelist.openNewTab": "Open in a new tab", + "medical_pricelist.addNewPriceListTooltip": "Add a new pricelist", + "medical_pricelist.deletePricelistTooltip": "Delete", + "medical_pricelist.pricelistForm.title": "Pricelist ({label})", + "medical_pricelist.pricelistForm.selectAll": "Select All", + "medical_pricelist.pricelistForm.table.title": "Details", + "medical_pricelist.pricelistForm.emptyTitle": "New Pricelist", + "medical_pricelist.table.code": "Code", + "medical_pricelist.table.overrule": "Price", + "medical_pricelist.table.price": "Price", + "medical_pricelist.table.name": "Name", + "medical_pricelist.table.type": "Type", + "medical_pricelist.table.type.c": "Curative", + "medical_pricelist.table.type.p": "Preventative", + "medical_pricelist.table.type.d": "Drug", + "medical_pricelist.table.editOverruleButton": "Edit", + "medical_pricelist.priceOverruleDialog.title": "Override Price", + "medical_pricelist.priceOverruleDialog.message": "You can override the price of this element for this pricelist", + "medical_pricelist.priceOverruleDialog.yes.button": "Save", + "medical_pricelist.priceOverruleDialog.clear.button": "Clear", + "medical_pricelist.priceOverruleDialog.input": "Price", + "medical_pricelist.createPricelist.mutationLabel": "Pricelist created - {name}", + "medical_pricelist.updatePricelist.mutationLabel": "Pricelist updated - {name}", + "medical_pricelist.deletePricelist.mutationLabel": "Pricelist deleted - {name}", + "medical_pricelist.deletePricelistDialog.title": "Delete pricelist", + "medical_pricelist.deletePricelistDialog.message": "Are you sure you want to delete the pricelist {name}?" } From 942d7d1d3f9bd2373d01a2c6c99ca1c08be6fe63 Mon Sep 17 00:00:00 2001 From: Eric Darchis Date: Mon, 30 Aug 2021 17:53:20 +0200 Subject: [PATCH 4/8] Case sensitivity of Price[lL]ist --- src/pickers/{ItemsPriceListPicker.js => ItemsPricelistPicker.js} | 0 src/pickers/{PriceListPicker.js => PricelistPicker.js} | 0 .../{ServicesPriceListPicker.js => ServicesPricelistPicker.js} | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename src/pickers/{ItemsPriceListPicker.js => ItemsPricelistPicker.js} (100%) rename src/pickers/{PriceListPicker.js => PricelistPicker.js} (100%) rename src/pickers/{ServicesPriceListPicker.js => ServicesPricelistPicker.js} (100%) diff --git a/src/pickers/ItemsPriceListPicker.js b/src/pickers/ItemsPricelistPicker.js similarity index 100% rename from src/pickers/ItemsPriceListPicker.js rename to src/pickers/ItemsPricelistPicker.js diff --git a/src/pickers/PriceListPicker.js b/src/pickers/PricelistPicker.js similarity index 100% rename from src/pickers/PriceListPicker.js rename to src/pickers/PricelistPicker.js diff --git a/src/pickers/ServicesPriceListPicker.js b/src/pickers/ServicesPricelistPicker.js similarity index 100% rename from src/pickers/ServicesPriceListPicker.js rename to src/pickers/ServicesPricelistPicker.js From d6bebd2abd892089e2758939f8f00b5374c8aefb Mon Sep 17 00:00:00 2001 From: Eric Darchis Date: Mon, 20 Sep 2021 17:29:33 +0200 Subject: [PATCH 5/8] v1.3.0-rc2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ca4981..7e67cba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openimis/fe-medical_pricelist", - "version": "1.3.0-rc1", + "version": "1.3.0-rc2", "license": "AGPL-3.0-only", "description": "openIMIS Frontend Medical Price List reference module", "repository": "openimis/openimis-fe-medical_pricelist_js", From 4889912fe3963c26503990b9ef4b21a247e4bc58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20G=C3=A9r=C3=B4me?= Date: Mon, 20 Sep 2021 18:10:57 +0200 Subject: [PATCH 6/8] fix(Location): Location is not required --- src/components/PricelistForm.js | 2 +- src/components/PricelistGeneralPanel.js | 1 - src/components/PricelistsSearcher.js | 20 ++++---------------- 3 files changed, 5 insertions(+), 18 deletions(-) diff --git a/src/components/PricelistForm.js b/src/components/PricelistForm.js index 3ee6699..b25d575 100644 --- a/src/components/PricelistForm.js +++ b/src/components/PricelistForm.js @@ -6,7 +6,7 @@ import ReplayIcon from "@material-ui/icons/Replay"; const PricelistForm = (props) => { const { readOnly, onBack, onSave, onReset, pricelist, onChange, fetchDetails, details } = props; - const canSave = () => pricelist.name && pricelist.location && pricelist.pricelistDate; + const canSave = () => pricelist.name && pricelist.pricelistDate; return ( <> Boolean(row.validityTo); const isRowLocked = () => false; -const getLocationByType = (location, type) => { - if (location.type === type) { - return location; - } else if (location.parent?.type === type) { - return location.parent; - } else { - return null; - } +const formatLocation = (location) => { + return location ? `${location.code} - ${location.name}` : ""; }; const HEADERS = [ @@ -62,14 +56,8 @@ const PricelistsSearcher = (props) => { () => [ (pricelist) => pricelist.name, (pricelist) => formatDateFromISO(pricelist.pricelistDate), - (pricelist) => - getLocationByType(pricelist.location, "R") - ? `${getLocationByType(pricelist.location, "R").code} - ${getLocationByType(pricelist.location, "R").name}` - : null, - (pricelist) => - getLocationByType(pricelist.location, "D") - ? `${getLocationByType(pricelist.location, "D").code} - ${getLocationByType(pricelist.location, "D").name}` - : null, + (pricelist) => formatLocation(pricelist.location?.parent || pricelist.location), + (pricelist) => formatLocation(pricelist.location?.parent ? pricelist.location : null), (pricelist) => formatDateFromISO(pricelist.validityFrom), (pricelist) => formatDateFromISO(pricelist.validityTo), (pricelist) => ( From c472097762d2a897cd1a0e3111c0f07ec84abc02 Mon Sep 17 00:00:00 2001 From: Eric Darchis Date: Tue, 21 Sep 2021 14:47:58 +0200 Subject: [PATCH 7/8] v1.3.0-rc3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e67cba..adb0f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@openimis/fe-medical_pricelist", - "version": "1.3.0-rc2", + "version": "1.3.0-rc3", "license": "AGPL-3.0-only", "description": "openIMIS Frontend Medical Price List reference module", "repository": "openimis/openimis-fe-medical_pricelist_js", From 5f141bc1e4c6b9b0787db5bcdfed06178ef03d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20G=C3=A9r=C3=B4me?= Date: Thu, 7 Oct 2021 15:56:06 +0200 Subject: [PATCH 8/8] remove console.log --- src/reducer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/reducer.js b/src/reducer.js index 9fd3ab8..a29ee86 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -151,7 +151,6 @@ function reducer( }, }; case "MEDICAL_PRICELIST_PRICELIST_RESP": - console.log(action); return { ...state, pricelists: {