diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuation.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuation.js new file mode 100644 index 000000000..a40d58281 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuation.js @@ -0,0 +1,86 @@ +import React, { useEffect, useState, useCallback } from 'react'; +import moment from 'moment'; + +import 'style/pages/FinancialStatements/InventoryValuation.scss'; + +import { InventoryValuationProvider } from './InventoryValuationProvider'; +import InventoryValuationActionsBar from './InventoryValuationActionsBar'; +import InventoryValuationHeader from './InventoryValuationHeader'; +import InventoryValuationTable from './InventoryValuationTable'; + +import DashboardPageContent from 'components/Dashboard/DashboardPageContent'; +import { InventoryValuationLoadingBar } from './components'; +import withInventoryValuationActions from './withInventoryValuationActions'; +import withSettings from 'containers/Settings/withSettings'; + +import { compose } from 'utils'; + +/** + * Inventory valuation. + */ +function InventoryValuation({ + // #withPreferences + organizationName, + + // #withInventoryValuationActions + toggleInventoryValuationFilterDrawer, +}) { + const [filter, setFilter] = useState({ + asDate: moment().endOf('day').format('YYYY-MM-DD'), + }); + + // Handle filter form submit. + const handleFilterSubmit = useCallback((filter) => { + const _filter = { + ...filter, + asDate: moment(filter.asDate).format('YYYY-MM-DD'), + }; + setFilter(_filter); + }, []); + + // Handle number format form submit. + const handleNumberFormatSubmit = (numberFormat) => { + setFilter({ + ...filter, + numberFormat, + }); + }; + + // Hide the filter drawer once the page unmount. + useEffect( + () => () => { + toggleInventoryValuationFilterDrawer(false); + }, + [toggleInventoryValuationFilterDrawer], + ); + + + return ( + + + + + +
+ +
+ +
+
+
+
+ ); +} + +export default compose( + withInventoryValuationActions, + withSettings(({ organizationSettings }) => ({ + organizationName: organizationSettings?.name, + })), +)(InventoryValuation); diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js new file mode 100644 index 000000000..52a700780 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationActionsBar.js @@ -0,0 +1,128 @@ +import React from 'react'; +import { + NavbarGroup, + Button, + Classes, + NavbarDivider, + Popover, + PopoverInteractionKind, + Position, +} from '@blueprintjs/core'; +import classNames from 'classnames'; +import { FormattedMessage as T } from 'react-intl'; + +import { Icon } from 'components'; +import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar'; +import NumberFormatDropdown from 'components/NumberFormatDropdown'; + +import withInventoryValuation from './withInventoryValuation'; +import withInventoryValuationActions from './withInventoryValuationActions'; +import { useInventoryValuationContext } from './InventoryValuationProvider'; + +import { compose, saveInvoke } from 'utils'; + +function InventoryValuationActionsBar({ + // #withInventoryValuation + isFilterDrawerOpen, + + // #withInventoryValuationActions + toggleInventoryValuationFilterDrawer, + + // #ownProps + numberFormat, + onNumberFormatSubmit, +}) { + const { refetchSheet, isLoading } = useInventoryValuationContext(); + + // Handle filter toggle click. + const handleFilterToggleClick = () => { + toggleInventoryValuationFilterDrawer(); + }; + + // Handle re-calc button click. + const handleRecalculateReport = () => { + refetchSheet(); + }; + + // Handle number format submit. + const handleNumberFormatSubmit = (numberFormat) => { + saveInvoke(onNumberFormatSubmit, numberFormat); + }; + + return ( + + + + + + + + + ); +} + +export default compose( + withInventoryValuation(({ inventoryValuationDrawerFilter }) => ({ + isFilterDrawerOpen: inventoryValuationDrawerFilter, + })), + withInventoryValuationActions, +)(InventoryValuationHeader); diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeaderGeneralPanel.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeaderGeneralPanel.js new file mode 100644 index 000000000..1059c2a67 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeaderGeneralPanel.js @@ -0,0 +1,47 @@ +import React from 'react'; +import { FastField } from 'formik'; +import { DateInput } from '@blueprintjs/datetime'; +import { FormGroup, Position } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'react-intl'; +import { Row, Col, FieldHint } from 'components'; +import { + momentFormatter, + tansformDateValue, + inputIntent, + handleDateChange, +} from 'utils'; + +/** + * inventory valuation - Drawer Header - General panel. + */ +export default function InventoryValuationHeaderGeneralPanel() { + return ( +
+ + + + {({ form, field: { value }, meta: { error } }) => ( + } + labelInfo={} + fill={true} + intent={inputIntent({ error })} + > + { + form.setFieldValue('asDate', selectedDate); + })} + popoverProps={{ position: Position.BOTTOM, minimal: true }} + minimal={true} + fill={true} + /> + + )} + + + +
+ ); +} diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationProvider.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationProvider.js new file mode 100644 index 000000000..d765db9b6 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationProvider.js @@ -0,0 +1,40 @@ +import React from 'react'; +import FinancialReportPage from '../FinancialReportPage'; +import { useInventoryValuation } from 'hooks/query'; +import { transformFilterFormToQuery } from '../common'; + +const InventoryValuationContext = React.createContext(); + +function InventoryValuationProvider({ query, ...props }) { + const { + data: inventoryValuation, + isFetching, + isLoading, + refetch, + } = useInventoryValuation( + { + ...transformFilterFormToQuery(query), + }, + { + keepPreviousData: true, + }, + ); + + const provider = { + inventoryValuation, + isLoading, + isFetching, + refetchSheet: refetch, + }; + + return ( + + + + ); +} + +const useInventoryValuationContext = () => + React.useContext(InventoryValuationContext); + +export { InventoryValuationProvider, useInventoryValuationContext }; diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationTable.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationTable.js new file mode 100644 index 000000000..177a406e0 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationTable.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { useIntl } from 'react-intl'; + +import FinancialSheet from 'components/FinancialSheet'; +import { DataTable } from 'components'; + +import { useInventoryValuationContext } from './InventoryValuationProvider'; +import { useInventoryValuationTableColumns } from './components'; + +/** + * inventory valuation data table. + */ +export default function InventoryValuationTable({ + //#ownProps + companyName, +}) { + const { formatMessage } = useIntl(); + + // inventory valuation context. + const { + inventoryValuation: { tableRows }, + isLoading, + } = useInventoryValuationContext(); + + // inventory valuation table columns. + const columns = useInventoryValuationTableColumns(); + + const rowClassNames = (row) => { + const { original } = row; + const rowTypes = Array.isArray(original.rowType) + ? original.rowType + : [original.rowType]; + + return { + ...rowTypes.reduce((acc, rowType) => { + acc[`row_type--${rowType}`] = rowType; + return acc; + }, {}), + }; + }; + + return ( + + + + ); +} diff --git a/client/src/containers/FinancialStatements/InventoryValuation/components.js b/client/src/containers/FinancialStatements/InventoryValuation/components.js new file mode 100644 index 000000000..c8fc1eb06 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/components.js @@ -0,0 +1,76 @@ +import React, { useMemo } from 'react'; +import { useIntl } from 'react-intl'; +import { getColumnWidth } from 'utils'; +import { If } from 'components'; +import { CellTextSpan } from 'components/Datatable/Cells'; +import { useInventoryValuationContext } from './InventoryValuationProvider'; +import FinancialLoadingBar from '../FinancialLoadingBar'; + +/** + * Retrieve inventory valuation table columns. + */ + +export const useInventoryValuationTableColumns = () => { + const { formatMessage } = useIntl(); + + // inventory valuation context + const { + inventoryValuation: { tableRows }, + } = useInventoryValuationContext(); + + return useMemo( + () => [ + { + Header: formatMessage({ id: 'item_name' }), + accessor: (row) => (row.code ? `${row.name} - ${row.code}` : row.name), + className: 'name', + width: 240, + textOverview: true, + }, + { + Header: formatMessage({ id: 'quantity' }), + accessor: 'quantity_formatted', + Cell: CellTextSpan, + className: 'quantity_formatted', + width: getColumnWidth(tableRows, `quantity_formatted`, { + minWidth: 120, + }), + textOverview: true, + }, + { + Header: formatMessage({ id: 'asset_value' }), + accessor: 'valuation_formatted', + Cell: CellTextSpan, + className: 'valuation', + width: getColumnWidth(tableRows, `valuation_formatted`, { + minWidth: 120, + }), + textOverview: true, + }, + { + Header: formatMessage({ id: 'average' }), + accessor: 'average_formatted', + Cell: CellTextSpan, + className: 'average_formatted', + width: getColumnWidth(tableRows, `average_formatted`, { + minWidth: 120, + }), + textOverview: true, + }, + ], + [tableRows, formatMessage], + ); +}; + +/** + * inventory valuation progress loading bar. + */ +export function InventoryValuationLoadingBar() { + const { isFetching } = useInventoryValuationContext(); + + return ( + + + + ); +} diff --git a/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuation.js b/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuation.js new file mode 100644 index 000000000..50d41de02 --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuation.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { getInventoryValuationFilterDrawer } from 'store/financialStatement/financialStatements.selectors'; + +export default (mapState) => { + const mapStateToProps = (state, props) => { + const mapped = { + inventoryValuationDrawerFilter: getInventoryValuationFilterDrawer(state), + }; + return mapState ? mapState(mapped, state, props) : mapped; + }; + return connect(mapStateToProps); +}; diff --git a/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuationActions.js b/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuationActions.js new file mode 100644 index 000000000..3fa006bed --- /dev/null +++ b/client/src/containers/FinancialStatements/InventoryValuation/withInventoryValuationActions.js @@ -0,0 +1,9 @@ +import { connect } from 'react-redux'; +import { toggleInventoryValuationFilterDrawer } from 'store/financialStatement/financialStatements.actions'; + +export const mapDispatchToProps = (dispatch) => ({ + toggleInventoryValuationFilterDrawer: (toggle) => + dispatch(toggleInventoryValuationFilterDrawer(toggle)), +}); + +export default connect(null, mapDispatchToProps); diff --git a/client/src/style/pages/FinancialStatements/InventoryValuation.scss b/client/src/style/pages/FinancialStatements/InventoryValuation.scss new file mode 100644 index 000000000..44623b6f2 --- /dev/null +++ b/client/src/style/pages/FinancialStatements/InventoryValuation.scss @@ -0,0 +1,27 @@ +.financial-sheet { + &--inventory-valuation { + min-width: 800px; + + .financial-sheet__table { + .thead, + .tbody { + .tr .td:not(:first-child), + .tr .th:not(:first-child) { + text-align: right; + } + } + .tbody { + .tr .td { + border-bottom: 0; + padding-top: 0.4rem; + padding-bottom: 0.4rem; + } + .tr.row_type--total .td { + border-top: 1px solid #000; + font-weight: 500; + border-bottom: 3px double #000; + } + } + } + } +}