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 (
+
+
+ }
+ />
+
+
+ }
+ text={
+ isFilterDrawerOpen ? (
+
+ ) : (
+
+ )
+ }
+ onClick={handleFilterToggleClick}
+ active={isFilterDrawerOpen}
+ />
+
+
+ }
+ minimal={true}
+ interactionKind={PopoverInteractionKind.CLICK}
+ position={Position.BOTTOM_LEFT}
+ >
+ }
+ icon={}
+ />
+
+
+
+ }
+ icon={}
+ />
+
+
+ }
+ text={}
+ />
+ }
+ text={}
+ />
+
+
+ );
+}
+
+export default compose(
+ withInventoryValuation(({ inventoryValuationDrawerFilter }) => ({
+ isFilterDrawerOpen: inventoryValuationDrawerFilter,
+ })),
+ withInventoryValuationActions,
+)(InventoryValuationActionsBar);
diff --git a/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeader.js b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeader.js
new file mode 100644
index 000000000..09aa658a3
--- /dev/null
+++ b/client/src/containers/FinancialStatements/InventoryValuation/InventoryValuationHeader.js
@@ -0,0 +1,94 @@
+import React from 'react';
+import * as Yup from 'yup';
+import moment from 'moment';
+import { FormattedMessage as T, useIntl } from 'react-intl';
+import { Formik, Form } from 'formik';
+import { Tabs, Tab, Button, Intent } from '@blueprintjs/core';
+
+import FinancialStatementHeader from 'containers/FinancialStatements/FinancialStatementHeader';
+import InventoryValuationHeaderGeneralPanel from './InventoryValuationHeaderGeneralPanel';
+import withInventoryValuation from './withInventoryValuation';
+import withInventoryValuationActions from './withInventoryValuationActions';
+
+import { compose } from 'utils';
+
+/**
+ * inventory valuation header.
+ */
+function InventoryValuationHeader({
+ // #ownProps
+ pageFilter,
+ onSubmitFilter,
+
+ // #withInventoryValuation
+ isFilterDrawerOpen,
+
+ // #withInventoryValuationActions
+ toggleInventoryValuationFilterDrawer,
+}) {
+ const { formatMessage } = useIntl();
+
+ // Form validation schema.
+ const validationSchema = Yup.object().shape({
+ as_date: Yup.date().required().label('asDate'),
+ });
+
+ // Initial values.
+ const initialValues = {
+ as_date: moment(pageFilter.asDate).toDate(),
+ };
+
+ const handleSubmit = (values, { setSubmitting }) => {
+ onSubmitFilter(values);
+ toggleInventoryValuationFilterDrawer(false);
+ setSubmitting(false);
+ };
+
+ // Handle drawer close action.
+ const handleDrawerClose = () => {
+ toggleInventoryValuationFilterDrawer(false);
+ };
+
+ // Handle cancel button click.
+ const handleCancelClick = () => {
+ toggleInventoryValuationFilterDrawer(false);
+ };
+
+ 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;
+ }
+ }
+ }
+ }
+}