From f6b14793fcae1535db6f1f6060c9880b172d7d81 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Tue, 22 Feb 2022 12:09:20 +0100 Subject: [PATCH 1/3] OPL-63: added Bill Event Tab --- src/actions.js | 43 ++++++++- src/components/BillEventsFilter.js | 59 ++++++++++++ src/components/BillEventsSearcher.js | 125 ++++++++++++++++++++++++++ src/components/BillEventsTab.js | 40 +++++++++ src/constants.js | 1 + src/dialogs/BillEventMessageDialog.js | 97 ++++++++++++++++++++ src/index.js | 5 +- src/reducer.js | 41 +++++++++ src/translations/en.json | 21 +++++ 9 files changed, 428 insertions(+), 4 deletions(-) create mode 100644 src/components/BillEventsFilter.js create mode 100644 src/components/BillEventsSearcher.js create mode 100644 src/components/BillEventsTab.js create mode 100644 src/dialogs/BillEventMessageDialog.js diff --git a/src/actions.js b/src/actions.js index 00d7bba..e152e67 100644 --- a/src/actions.js +++ b/src/actions.js @@ -169,6 +169,13 @@ const formatBillPaymentGQL = (payment) => ${!!payment.paymentOrigin ? `paymentOrigin: "${payment.paymentOrigin}"` : ""} `; +const formatBillEventMessageGQL = (eventMessage) => + ` + ${!!eventMessage.billId ? `billId: "${eventMessage.billId}"` : ""} + ${!!eventMessage.eventType ? `eventType: ${eventMessage.eventType}` : ""} + ${!!eventMessage.message ? `message: "${eventMessage.message}"` : ""} + `; + export function fetchInvoices(params) { const payload = formatPageQueryWithCount("invoice", params, INVOICE_FULL_PROJECTION); return graphql(payload, ACTION_TYPE.SEARCH_INVOICES); @@ -313,7 +320,11 @@ export function fetchBillPayments(params) { } export function createBillPayment(billPayment, clientMutationLabel) { - const mutation = formatMutation("createBillPayment", formatBillPaymentGQL(billPayment), clientMutationLabel); + const mutation = formatMutation( + "createBillPayment", + formatBillPaymentGQL(billPayment), + clientMutationLabel + ); const requestedDateTime = new Date(); return graphql( mutation.payload, @@ -328,7 +339,11 @@ export function createBillPayment(billPayment, clientMutationLabel) { } export function updateBillPayment(billPayment, clientMutationLabel) { - const mutation = formatMutation("updateBillPayment", formatBillPaymentGQL(billPayment), clientMutationLabel); + const mutation = formatMutation( + "updateBillPayment", + formatBillPaymentGQL(billPayment), + clientMutationLabel + ); const requestedDateTime = new Date(); return graphql( mutation.payload, @@ -357,3 +372,27 @@ export function deleteBillPayment(billPayment, clientMutationLabel) { }, ); } + +export function fetchBillEvents(params) { + const payload = formatPageQueryWithCount("billEvent", params, INVOICE_EVENT_FULL_PROJECTION); + return graphql(payload, ACTION_TYPE.SEARCH_BILL_EVENTS); +} + +export function createBillEventType(billEvent, clientMutationLabel) { + const mutation = formatMutation( + "createBillEventType", + formatBillEventMessageGQL(billEvent), + clientMutationLabel + ); + const requestedDateTime = new Date(); + return graphql( + mutation.payload, + [REQUEST(ACTION_TYPE.MUTATION), SUCCESS(ACTION_TYPE.CREATE_BILL_EVENT_MESSAGE), ERROR(ACTION_TYPE.MUTATION)], + { + actionType: ACTION_TYPE.CREATE_BILL_EVENT_MESSAGE, + clientMutationId: mutation.clientMutationId, + clientMutationLabel, + requestedDateTime, + }, + ); +} diff --git a/src/components/BillEventsFilter.js b/src/components/BillEventsFilter.js new file mode 100644 index 0000000..b56e125 --- /dev/null +++ b/src/components/BillEventsFilter.js @@ -0,0 +1,59 @@ +import React from "react"; +import { injectIntl } from "react-intl"; +import { withModulesManager, formatMessage, TextInput } from "@openimis/fe-core"; +import { Grid } from "@material-ui/core"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { CONTAINS_LOOKUP, DEFUALT_DEBOUNCE_TIME } from "../constants"; +import _debounce from "lodash/debounce"; +import { defaultFilterStyles } from "../util/styles"; +import InvoiceEventTypePicker from "../pickers/InvoiceEventTypePicker"; + +const BillEventsFilter = ({intl, classes, filters, onChangeFilters }) => { + const debouncedOnChangeFilters = _debounce(onChangeFilters, DEFUALT_DEBOUNCE_TIME); + + const filterValue = (filterName) => filters?.[filterName]?.value; + + const onChangeStringFilter = + (filterName, lookup = null) => + (value) => { + lookup + ? debouncedOnChangeFilters([ + { + id: filterName, + value, + filter: `${filterName}_${lookup}: "${value}"`, + }, + ]) + : onChangeFilters([ + { + id: filterName, + value, + filter: `${filterName}: "${value}"`, + }, + ]); + }; + + return ( + + + + + + + + + ); + }; + + export default injectIntl(withTheme(withStyles(defaultFilterStyles)(BillEventsFilter))); diff --git a/src/components/BillEventsSearcher.js b/src/components/BillEventsSearcher.js new file mode 100644 index 0000000..0ffad49 --- /dev/null +++ b/src/components/BillEventsSearcher.js @@ -0,0 +1,125 @@ +import React, { useRef, useEffect, useState } from "react"; +import { injectIntl } from "react-intl"; +import { formatMessageWithValues, Searcher } from "@openimis/fe-core"; +import { bindActionCreators } from "redux"; +import { connect } from "react-redux"; +import { fetchBillEvents } from "../actions"; +import { DEFAULT_PAGE_SIZE, ROWS_PER_PAGE_OPTIONS} from "../constants"; +import BillEventsFilter from "./BillEventsFilter"; +import InvoiceEventTypePicker from "../pickers/InvoiceEventTypePicker" +import { ACTION_TYPE } from "../reducer"; + +const BillEventsSearcher = ({ + intl, + bill, + submittingMutation, + mutation, + fetchBillEvents, + fetchingBillEvents, + fetchedBillEvents, + errorBillEvents, + billEvents, + billEventsPageInfo, + billEventsTotalCount, +}) => { + const [queryParams, setQueryParams] = useState([]); + const prevSubmittingMutationRef = useRef(); + + useEffect(() => { + if ( + prevSubmittingMutationRef.current && + !submittingMutation && + mutation?.actionType === ACTION_TYPE.CREATE_BILL_EVENT_MESSAGE + ) { + refetch(); + } + }, [submittingMutation]); + + useEffect(() => { + prevSubmittingMutationRef.current = submittingMutation; + }); + + const fetch = (params) => fetchBillEvents(params); + + const refetch = () => fetch(queryParams); + + const filtersToQueryParams = ({ filters, pageSize, beforeCursor, afterCursor, orderBy }) => { + const queryParams = Object.keys(filters) + .filter((f) => !!filters[f]["filter"]) + .map((f) => filters[f]["filter"]); + pageSize && queryParams.push(`first: ${pageSize}`); + beforeCursor && queryParams.push(`before: "${beforeCursor}"`); + afterCursor && queryParams.push(`after: "${afterCursor}"`); + orderBy && queryParams.push(`orderBy: ["${orderBy}"]`); + setQueryParams(queryParams); + return queryParams; + }; + + const headers = () => [ + "billEvent.eventType.label", + "billEvent.message", + ]; + + const itemFormatters = () => [ + (billEvent) => , + (billEvent) => billEvent.message, + ]; + + const sorts = () => [ + ["eventType", true], + ["message", true], + ]; + + const defaultFilters = () => ({ + bill_Id: { + value: bill?.id, + filter: `bill_Id: "${bill?.id}"`, + }, + isDeleted: { + value: false, + filter: "isDeleted: false", + }, + }); + + return ( + !!bill?.id && ( + + ) + ); +}; + +const mapStateToProps = (state) => ({ + fetchingBillEvents: state.invoice.fetchingBillEvents, + fetchedBillEvents: state.invoice.fetchedBillEvents, + errorBillEvents: state.invoice.errorBillEvents, + billEvents: state.invoice.billEvents, + billEventsPageInfo: state.invoice.billEventsPageInfo, + billEventsTotalCount: state.invoice.billEventsTotalCount, + submittingMutation: state.invoice.submittingMutation, + mutation: state.invoice.mutation, + confirmed: state.core.confirmed, +}); + +const mapDispatchToProps = (dispatch) => bindActionCreators({ fetchBillEvents }, dispatch); + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(BillEventsSearcher)); diff --git a/src/components/BillEventsTab.js b/src/components/BillEventsTab.js new file mode 100644 index 0000000..4046a6e --- /dev/null +++ b/src/components/BillEventsTab.js @@ -0,0 +1,40 @@ +import React from "react"; +import { Tab, Grid, Typography } from "@material-ui/core"; +import { formatMessage, PublishedComponent, FormattedMessage } from "@openimis/fe-core"; +import { BILL_EVENTS_TAB_VALUE, RIGHT_BILL_EVENT_SEARCH, RIGHT_BILL_EVENT_CREATE_MESSAGE } from "../constants"; +import BillEventsSearcher from "./BillEventsSearcher"; +import CreateBillEventMessageDialog from "../dialogs/BillEventMessageDialog"; + + +const BillEventsTabLabel = ({ intl, rights, onChange, tabStyle, isSelected }) => + rights?.includes(RIGHT_BILL_EVENT_SEARCH) && ( + + ); + + +const BillEventsTabPanel = ({ rights, value, bill }) => ( + + {rights?.includes(RIGHT_BILL_EVENT_CREATE_MESSAGE) && ( + + + + + + + + + + + )} + + +); + + +export { BillEventsTabLabel, BillEventsTabPanel }; diff --git a/src/constants.js b/src/constants.js index bd8d5a3..24793f4 100644 --- a/src/constants.js +++ b/src/constants.js @@ -109,6 +109,7 @@ export const BILL_TABS_PANEL_CONTRIBUTION_KEY = "bill.TabPanel.panel"; export const BILL_TABS_LABEL_CONTRIBUTION_KEY = "bill.TabPanel.label"; export const BILL_LINE_ITEMS_TAB_VALUE = "billLineItemsTab"; export const BILL_PAYMENTS_TAB_VALUE = "billPaymentsTab"; +export const BILL_EVENTS_TAB_VALUE = "billEventsTab"; export const EMPTY_PAYMENT = { status: null, diff --git a/src/dialogs/BillEventMessageDialog.js b/src/dialogs/BillEventMessageDialog.js new file mode 100644 index 0000000..14ac79f --- /dev/null +++ b/src/dialogs/BillEventMessageDialog.js @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { injectIntl } from "react-intl"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogTitle from "@material-ui/core/DialogTitle"; +import AddIcon from "@material-ui/icons/Add"; +import { + FormattedMessage, + TextInput, + formatMessageWithValues, +} from "@openimis/fe-core"; +import { Fab, Grid } from "@material-ui/core"; +import { withTheme, withStyles } from "@material-ui/core/styles"; +import { createBillEventType } from "../actions"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import { EMPTY_EVENT_MESSAGE } from "../constants"; +import { defaultDialogStyles } from "../util/styles"; + + +const BillEventMessageDialog = ({ + intl, + classes, + bill, + createBillEventType +}) => { + const [isOpen, setIsOpen] = useState(false); + const [eventMessage, setEventMessage] = useState({ billId: bill.id, ...EMPTY_EVENT_MESSAGE }); + + const handleOpen = () => setIsOpen(true); + + const handleClose = () => { + setIsOpen(false); + setEventMessage({ billId: eventMessage.billId, ...EMPTY_EVENT_MESSAGE }); + }; + + const handleSave = () => { + createBillEventType( + eventMessage, + formatMessageWithValues(intl, "invoice", "billEventMessage.create.mutationLabel", { + billCode: bill?.code, + }), + ); + handleClose(); + }; + + const onAttributeChange = (attribute) => (value) => + setEventMessage({ + ...eventMessage, + [attribute]: value, + }); + + const canSave = !!eventMessage?.message; + + return ( + <> + + + + + + + + + + + + + + + + + + + + + ); +}; + +const mapDispatchToProps = (dispatch) => bindActionCreators({ createBillEventType }, dispatch); + +export default injectIntl( + withTheme(withStyles(defaultDialogStyles)(connect(null, mapDispatchToProps)(BillEventMessageDialog))), + ); + \ No newline at end of file diff --git a/src/index.js b/src/index.js index 54efd79..4972bb6 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import { InvoicePaymentsTabLabel, InvoicePaymentsTabPanel } from "./components/I import { InvoiceEventsTabLabel, InvoiceEventsTabPanel } from "./components/InvoiceEventsTab"; import { BillLineItemsTabLabel, BillLineItemsTabPanel } from "./components/BillLineItemsTab"; import { BillPaymentsTabLabel, BillPaymentsTabPanel } from "./components/BillPaymentsTab"; +import { BillEventsTabLabel, BillEventsTabPanel } from "./components/BillEventsTab"; const ROUTE_INVOICES = "invoices"; const ROUTE_INVOICE = "invoices/invoice"; @@ -35,8 +36,8 @@ const DEFAULT_CONFIG = { ], "invoice.TabPanel.label": [InvoiceLineItemsTabLabel, InvoicePaymentsTabLabel, InvoiceEventsTabLabel], "invoice.TabPanel.panel": [InvoiceLineItemsTabPanel, InvoicePaymentsTabPanel, InvoiceEventsTabPanel], - "bill.TabPanel.label": [BillLineItemsTabLabel, BillPaymentsTabLabel], - "bill.TabPanel.panel": [BillLineItemsTabPanel, BillPaymentsTabPanel], + "bill.TabPanel.label": [BillLineItemsTabLabel, BillPaymentsTabLabel, BillEventsTabLabel], + "bill.TabPanel.panel": [BillLineItemsTabPanel, BillPaymentsTabPanel, BillEventsTabPanel], }; export const InvoiceModule = (cfg) => { diff --git a/src/reducer.js b/src/reducer.js index cc22568..829ed95 100644 --- a/src/reducer.js +++ b/src/reducer.js @@ -31,6 +31,8 @@ export const ACTION_TYPE = { CREATE_BILL_PAYMENT: "BILL_CREATE_BILL_PAYMENT", UPDATE_BILL_PAYMENT: "BILL_UPDATE_BILL_PAYMENT", DELETE_BILL_PAYMENT: "BILL_DELETE_BILL_PAYMENT", + SEARCH_BILL_EVENTS: "BILL_BILL_EVENTS", + CREATE_BILL_EVENT_MESSAGE: "BILL_CREATE_BILL_EVENT_MESSAGE", }; function reducer( @@ -76,6 +78,7 @@ function reducer( errorBill: null, fetchedBill: false, bill: null, + fetchingBillLineItems: false, errorBillLineItems: null, fetchedBillLineItems: false, @@ -89,6 +92,13 @@ function reducer( billPayments: [], billPaymentsPageInfo: {}, billPaymentsTotalCount: 0, + + fetchingBillEvents: false, + errorBillEvents: null, + fetchedBillEvents: false, + billEvents: [], + billEventsPageInfo: {}, + billEventsTotalCount: 0, }, action, ) { @@ -354,6 +364,35 @@ function reducer( fetchingBillPayments: false, errorBillPayments: formatServerError(action.payload), }; + case REQUEST(ACTION_TYPE.SEARCH_BILL_EVENTS): + return { + ...state, + fetchingBillEvents: true, + fetchedBillEvents: false, + billEvents: [], + billEventsPageInfo: {}, + billEventsTotalCount: 0, + errorBillEvents: null, + }; + case SUCCESS(ACTION_TYPE.SEARCH_BILL_EVENTS): + return { + ...state, + fetchingBillEvents: false, + fetchedBillEvents: true, + billEvents: parseData(action.payload.data.billEvent)?.map((billEvent) => ({ + ...billEvent, + eventType: getEnumValue(billEvent?.eventType), + })), + billEventsPageInfo: pageInfo(action.payload.data.billEvent), + billEventsTotalCount: action.payload.data.billEvent?.totalCount, + errorBillEvents: formatGraphQLError(action.payload), + }; + case ERROR(ACTION_TYPE.SEARCH_BILL_EVENTS): + return { + ...state, + fetchingBillEvents: false, + errorBillEvents: formatServerError(action.payload), + }; case REQUEST(ACTION_TYPE.MUTATION): return dispatchMutationReq(state, action); case ERROR(ACTION_TYPE.MUTATION): @@ -376,6 +415,8 @@ function reducer( return dispatchMutationResp(state, "updateBillPayment", action); case SUCCESS(ACTION_TYPE.DELETE_BILL_PAYMENT): return dispatchMutationResp(state, "deleteBillPayment", action); + case SUCCESS(ACTION_TYPE.CREATE_BILL_EVENT_MESSAGE): + return dispatchMutationResp(state, "createBillEventType", action); default: return state; } diff --git a/src/translations/en.json b/src/translations/en.json index 3da86a8..945530f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -227,5 +227,26 @@ }, "mutationLabel": "Delete Bill Payment {billPaymentLabel} of {billCode}" } + }, + "billEvents": { + "label": "Events", + "searcherResultsTitle":"{billEventsTotalCount} Events Found" + }, + "billEvent": { + "eventType": { + "label": "Type", + "MESSAGE": "Message", + "STATUS": "Status", + "WARNING": "Warning", + "PAYMENT": "Payment", + "PAYMENT_ERROR": "Payment Error" + }, + "message": "Message" + }, + "billEventMessage": { + "create": { + "label": "Create new Comment", + "mutationLabel": "Create Bill Event Message for {billCode}" + } } } From 25ba7c0efe4b2d191cb7196ea4870c6237865ccb Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Tue, 22 Feb 2022 12:11:09 +0100 Subject: [PATCH 2/3] OPL-63: fixed name of Dialog --- src/dialogs/InvoiceEventMessageDialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dialogs/InvoiceEventMessageDialog.js b/src/dialogs/InvoiceEventMessageDialog.js index 4dc4c74..59736d2 100644 --- a/src/dialogs/InvoiceEventMessageDialog.js +++ b/src/dialogs/InvoiceEventMessageDialog.js @@ -15,7 +15,7 @@ import { bindActionCreators } from "redux"; import { EMPTY_EVENT_MESSAGE } from "../constants"; import { defaultDialogStyles } from "../util/styles"; -const InvoicePaymentDialog = ({ intl, classes, invoice, createInvoiceEventMessage }) => { +const InvoiceEventMessageDialog = ({ intl, classes, invoice, createInvoiceEventMessage }) => { const [isOpen, setIsOpen] = useState(false); const [eventMessage, setEventMessage] = useState({ invoiceId: invoice.id, ...EMPTY_EVENT_MESSAGE }); @@ -82,5 +82,5 @@ const InvoicePaymentDialog = ({ intl, classes, invoice, createInvoiceEventMessag const mapDispatchToProps = (dispatch) => bindActionCreators({ createInvoiceEventMessage }, dispatch); export default injectIntl( - withTheme(withStyles(defaultDialogStyles)(connect(null, mapDispatchToProps)(InvoicePaymentDialog))), + withTheme(withStyles(defaultDialogStyles)(connect(null, mapDispatchToProps)(InvoiceEventMessageDialog))), ); From 5aad56367e06bf81877108e059334e4450492d80 Mon Sep 17 00:00:00 2001 From: sniedzielski Date: Tue, 22 Feb 2022 12:13:16 +0100 Subject: [PATCH 3/3] OPL-63: updated README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d5159a5..5ef8ca8 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ It is dedicated to be deployed as a module of [openimis-fe_js](https://github.co * `BILL_CREATE_BILL_PAYMENT_RESP` receiving a result of create Bill Payment mutation * `BILL_UPDATE_BILL_PAYMENT_RESP` receiving a result of update Bill Payment mutation * `BILL_DELETE_BILL_PAYMENT_RESP` receiving a result of delete Bill Payment mutation +* `BILL_BILL_EVENTS_{REQ|RESP|ERR}` fetching Bill Events (as triggered by the searcher) +* `BILL_CREATE_BILL_EVENT_MESSAGE_RESP` receiving a result of create Bill Event Message mutation ## Other Modules Listened Redux Actions None