From 6134ad5598f862d4a04cda426643cde8c67df8d5 Mon Sep 17 00:00:00 2001
From: elforjani13 <39470382+elforjani13@users.noreply.github.com>
Date: Thu, 30 Jun 2022 21:59:12 +0200
Subject: [PATCH] feat: add estimated expense.
---
src/common/modalChargeOptions.tsx | 18 ++-
src/components/DialogsContainer.js | 4 +
.../Projects/components/ChargeSelect.tsx | 50 ++++++++
.../Projects/components/ExpenseSelect.tsx | 67 +++++++++++
src/containers/Projects/components/index.ts | 2 +
.../EstimatedExpense.schema.tsx | 19 +++
.../EstimatedExpenseForm.tsx | 56 +++++++++
.../EstimatedExpenseFormChargeFields.tsx | 54 +++++++++
.../EstimatedExpenseFormConent.tsx | 17 +++
.../EstimatedExpenseFormDialogContent.tsx | 22 ++++
.../EstimatedExpenseFormFields.tsx | 111 ++++++++++++++++++
.../EstimatedExpenseFormFloatingActions.tsx | 48 ++++++++
.../EstimatedExpenseFormProvider.tsx | 31 +++++
.../EstimatedExpenseFormDialog/index.tsx | 55 +++++++++
14 files changed, 550 insertions(+), 4 deletions(-)
create mode 100644 src/containers/Projects/components/ChargeSelect.tsx
create mode 100644 src/containers/Projects/components/ExpenseSelect.tsx
create mode 100644 src/containers/Projects/components/index.ts
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpense.schema.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseForm.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormChargeFields.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormConent.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormDialogContent.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFields.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFloatingActions.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx
create mode 100644 src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx
diff --git a/src/common/modalChargeOptions.tsx b/src/common/modalChargeOptions.tsx
index 623feefec..05b737877 100644
--- a/src/common/modalChargeOptions.tsx
+++ b/src/common/modalChargeOptions.tsx
@@ -1,7 +1,17 @@
import intl from 'react-intl-universal';
-export const modalChargeOptions = [
- { name: 'Hourly rate', value: 'Hourly rate' },
- { name: 'Fixed price', value: 'Fixed price' },
- { name: 'Non-chargeable', value: 'Non-chargeable' },
+export const taskChargeOptions = [
+ { name: intl.get('task.dialog.hourly_rate'), value: 'Hourly rate' },
+ { name: intl.get('task.dialog.fixed_price'), value: 'Fixed price' },
+ { name: intl.get('task.dialog.non_chargeable'), value: 'Non-chargeable' },
+];
+
+export const expenseChargeOption = [
+ {
+ name: intl.get('expenses.dialog.markup'),
+ value: '% markup',
+ },
+ { name: intl.get('expenses.dialog.pass_cost_on'), value: 'Pass cost on' },
+ { name: intl.get('expemses.dialog.custom_price'), value: 'Custom Pirce' },
+ { name: intl.get('expenses.dialog.non_chargeable'), value: 'Non-chargeable' },
];
diff --git a/src/components/DialogsContainer.js b/src/components/DialogsContainer.js
index 014cbb847..d7c037f32 100644
--- a/src/components/DialogsContainer.js
+++ b/src/components/DialogsContainer.js
@@ -43,6 +43,8 @@ import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalan
import ProjectFormDialog from '../containers/Projects/containers/ProjectFormDialog';
import TaskFormDialog from '../containers/Projects/containers/TaskFormDialog';
import TimeEntryFormDialog from '../containers/Projects/containers/TimeEntryFormDialog';
+import ExpenseFormDialog from '../containers/Projects/containers/ExpenseFormDialog';
+import EstimatedExpenseFormDialog from '../containers/Projects/containers/EstimatedExpenseFormDialog';
/**
* Dialogs container.
@@ -96,6 +98,8 @@ export default function DialogsContainer() {
+
+
);
}
diff --git a/src/containers/Projects/components/ChargeSelect.tsx b/src/containers/Projects/components/ChargeSelect.tsx
new file mode 100644
index 000000000..2a11908c0
--- /dev/null
+++ b/src/containers/Projects/components/ChargeSelect.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { MenuItem, Button } from '@blueprintjs/core';
+import { FSelect } from 'components';
+
+/**
+ *
+ * @param {*}
+ * @param {*} param1
+ * @returns
+ */
+const chargeItemRenderer = (item, { handleClick, modifiers, query }) => {
+ return (
+
+ );
+};
+
+const chargeItemSelectProps = {
+ itemRenderer: chargeItemRenderer,
+ valueAccessor: 'value',
+ labelAccessor: 'name',
+};
+
+/**
+ *
+ * @param param0
+ * @returns
+ */
+export function ChargeSelect({ items, ...rest }) {
+ return (
+
+ );
+}
+/**
+ *
+ * @param param0
+ * @returns
+ */
+function ChargeSelectButton({ label }) {
+ return ;
+}
diff --git a/src/containers/Projects/components/ExpenseSelect.tsx b/src/containers/Projects/components/ExpenseSelect.tsx
new file mode 100644
index 000000000..28447d137
--- /dev/null
+++ b/src/containers/Projects/components/ExpenseSelect.tsx
@@ -0,0 +1,67 @@
+import React from 'react';
+import intl from 'react-intl-universal';
+import { MenuItem, Button } from '@blueprintjs/core';
+import { FSelect } from 'components';
+
+/**
+ *
+ * @param query
+ * @param expense
+ * @param _index
+ * @param exactMatch
+ */
+const expenseItemPredicate = (query, expense, _index, exactMatch) => {
+ const normalizedTitle = expense.name.toLowerCase();
+ const normalizedQuery = query.toLowerCase();
+
+ if (exactMatch) {
+ return normalizedTitle === normalizedQuery;
+ } else {
+ return `${expense.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
+ }
+};
+
+/**
+ *
+ * @param expense
+ * @param param1
+ * @returns
+ */
+const expenseItemRenderer = (expense, { handleClick, modifiers, query }) => {
+ return (
+
+ );
+};
+
+const expenseSelectProps = {
+ itemPredicate: expenseItemPredicate,
+ itemRenderer: expenseItemRenderer,
+ valueAccessor: 'id',
+ labelAccessor: 'name',
+};
+
+export function ExpenseSelect({ expenses, defaultText, ...rest }) {
+ return (
+
+ );
+}
+
+function ExpenseSelectButton({ label, ...rest }) {
+ return (
+
+ );
+}
diff --git a/src/containers/Projects/components/index.ts b/src/containers/Projects/components/index.ts
new file mode 100644
index 000000000..8a79d463f
--- /dev/null
+++ b/src/containers/Projects/components/index.ts
@@ -0,0 +1,2 @@
+export * from './ExpenseSelect';
+export * from './ChargeSelect';
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpense.schema.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpense.schema.tsx
new file mode 100644
index 000000000..2aada093e
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpense.schema.tsx
@@ -0,0 +1,19 @@
+import * as Yup from 'yup';
+import intl from 'react-intl-universal';
+import { DATATYPES_LENGTH } from 'common/dataTypes';
+
+const Schema = Yup.object().shape({
+ estimatedExpense: Yup.number().label(
+ intl.get('estimated_expense.schema.label.estimated_expense'),
+ ),
+ quantity: Yup.number().label(
+ intl.get('estimated_expense.schema.label.quantity'),
+ ),
+ unitPrice: Yup.number().label(
+ intl.get('estimated_expense.schema.label.unit_price'),
+ ),
+ total: Yup.number(),
+ charge: Yup.string(),
+});
+
+export const CreateEstimatedExpenseFormSchema = Schema;
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseForm.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseForm.tsx
new file mode 100644
index 000000000..3be835798
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseForm.tsx
@@ -0,0 +1,56 @@
+import React from 'react';
+import { Formik } from 'formik';
+import { AppToaster } from 'components';
+import { CreateEstimatedExpenseFormSchema } from './EstimatedExpense.schema';
+import EstimatedExpenseFormConent from './EstimatedExpenseFormConent';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+const defaultInitialValues = {
+ estimatedExpense: '',
+ unitPrice: '',
+ quantity: 1,
+ charge: '% markup',
+ percentage: '',
+};
+
+/**
+ * Estimated expense form dialog.
+ * @returns
+ */
+function EstimatedExpenseForm({
+ //#withDialogActions
+ closeDialog,
+}) {
+ const initialValues = {
+ ...defaultInitialValues,
+ };
+
+ // Handles the form submit.
+ const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
+ // Handle request response success.
+ const onSuccess = (response) => {
+ AppToaster.show({});
+ };
+
+ // Handle request response errors.
+ const onError = ({
+ response: {
+ data: { errors },
+ },
+ }) => {
+ setSubmitting(false);
+ };
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(EstimatedExpenseForm);
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormChargeFields.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormChargeFields.tsx
new file mode 100644
index 000000000..86c760eaf
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormChargeFields.tsx
@@ -0,0 +1,54 @@
+//@ts-nocheck
+import React from 'react';
+import intl from 'react-intl-universal';
+import { Classes, ControlGroup } from '@blueprintjs/core';
+import { FFormGroup, FInputGroup, Choose } from 'components';
+import { useFormikContext } from 'formik';
+
+function PercentageFormField() {
+ return (
+
+
+
+ );
+}
+
+function CustomPirceField() {
+ return (
+
+
+
+
+
+
+
+
+ );
+}
+
+/**
+ * estimate expense form charge fields.
+ * @returns
+ */
+export default function EstimatedExpenseFormChargeFields() {
+ const { values } = useFormikContext();
+ return (
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormConent.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormConent.tsx
new file mode 100644
index 000000000..055f3027d
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormConent.tsx
@@ -0,0 +1,17 @@
+import React from 'react';
+import { Form } from 'formik';
+import EstimatedExpenseFormFields from './EstimatedExpenseFormFields';
+import EstimatedExpenseFormFloatingActions from './EstimatedExpenseFormFloatingActions';
+
+/**
+ * Estimated expense form content.
+ * @returns
+ */
+export default function EstimatedExpenseFormConent() {
+ return (
+
+ );
+}
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormDialogContent.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormDialogContent.tsx
new file mode 100644
index 000000000..369a1b92b
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormDialogContent.tsx
@@ -0,0 +1,22 @@
+import React from 'react';
+import { EstimatedExpenseFormProvider } from './EstimatedExpenseFormProvider';
+import EstimatedExpenseForm from './EstimatedExpenseForm';
+
+/**
+ * Estimate expense form dialog.
+ * @return
+ */
+export default function EstimatedExpenseFormDialogContent({
+ //#ownProps
+ dialogName,
+ estimatedExpense,
+}) {
+ return (
+
+
+
+ );
+}
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFields.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFields.tsx
new file mode 100644
index 000000000..975903ca2
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFields.tsx
@@ -0,0 +1,111 @@
+//@ts-nocheck
+import React from 'react';
+import styled from 'styled-components';
+import intl from 'react-intl-universal';
+import { Classes, ControlGroup } from '@blueprintjs/core';
+import classNames from 'classnames';
+import {
+ FFormGroup,
+ FInputGroup,
+ FormattedMessage as T,
+ FieldRequiredHint,
+} from 'components';
+import { ExpenseSelect } from '../../components';
+import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
+import EstimatedExpenseFormChargeFields from './EstimatedExpenseFormChargeFields';
+import { ChargeSelect } from '../../components';
+import { expenseChargeOption } from 'common/modalChargeOptions';
+
+/**
+ * Estimated expense form fields.
+ * @returns
+ */
+export default function EstimatedExpenseFormFields() {
+ return (
+
+ {/*------------ Estimated Expense -----------*/}
+
+
+
+
+ {/*------------ Quantity -----------*/}
+
+
+
+
+ Cost to you
+ {/*------------ Unit Price -----------*/}
+
+
+
+
+
+
+
+
+
+ What you'll charge
+ {/*------------ Charge -----------*/}
+ }
+ className={classNames('form-group--select-list', Classes.FILL)}
+ >
+
+
+
+ {/*------------ Estimated Amount -----------*/}
+
+
+
+
+ 0.00
+
+
+ );
+}
+
+const MetaLineLabel = styled.div`
+ font-size: 14px;
+ line-height: 1.5rem;
+ font-weight: 500;
+ margin-bottom: 8px;
+`;
+
+const EstimatedAmountWrap = styled.div`
+ display: block;
+ text-align: right;
+`;
+const EstimatedAmountLabel = styled.span`
+ font-size: 14px;
+ line-height: 1.5rem;
+ opacity: 0.75;
+`;
+const EstimatedAmount = styled.span`
+ font-size: 15px;
+ font-weight: 700;
+ padding-left: 14px;
+ line-height: 2rem;
+`;
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFloatingActions.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFloatingActions.tsx
new file mode 100644
index 000000000..f3d55b4e3
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormFloatingActions.tsx
@@ -0,0 +1,48 @@
+//@ts-nocheck
+import React from 'react';
+import { useFormikContext } from 'formik';
+import { Intent, Button, Classes } from '@blueprintjs/core';
+import { FormattedMessage as T } from 'components';
+import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
+import withDialogActions from 'containers/Dialog/withDialogActions';
+import { compose } from 'utils';
+
+/**
+ * Estimated expense form floating actions.
+ * @returns
+ */
+function EstimatedExpenseFormFloatingActions({
+ // #withDialogActions
+ closeDialog,
+}) {
+ // Formik context.
+ const { isSubmitting } = useFormikContext();
+
+ // expense form dialog context.
+ const { dialogName } = useEstimatedExpenseFormContext();
+
+ // Handle close button click.
+ const handleCancelBtnClick = () => {
+ closeDialog(dialogName);
+ };
+
+ return (
+
+ );
+}
+
+export default compose(withDialogActions)(EstimatedExpenseFormFloatingActions);
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx
new file mode 100644
index 000000000..28b115d74
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/EstimatedExpenseFormProvider.tsx
@@ -0,0 +1,31 @@
+//@ts-nocheck
+import React from 'react';
+import { DialogContent } from 'components';
+
+const EstimatedExpenseFormContext = React.createContext();
+
+/**
+ * Estimated expense form provider.
+ * @returns
+ */
+function EstimatedExpenseFormProvider({
+ //#OwnProps
+ dialogName,
+ estimatedExpenseId,
+ ...props
+}) {
+ // state provider.
+ const provider = {
+ dialogName,
+ };
+ return (
+
+
+
+ );
+}
+
+const useEstimatedExpenseFormContext = () =>
+ React.useContext(EstimatedExpenseFormContext);
+
+export { EstimatedExpenseFormProvider, useEstimatedExpenseFormContext };
diff --git a/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx b/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx
new file mode 100644
index 000000000..388c7a27f
--- /dev/null
+++ b/src/containers/Projects/containers/EstimatedExpenseFormDialog/index.tsx
@@ -0,0 +1,55 @@
+import React from 'react';
+import styled from 'styled-components';
+import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
+import withDialogRedux from 'components/DialogReduxConnect';
+import { compose } from 'utils';
+
+const EstimatedExpenseFormDialogContent = React.lazy(
+ () => import('./EstimatedExpenseFormDialogContent'),
+);
+
+/**
+ * Estimate expense form dialog.
+ * @returns
+ */
+function EstimatedExpenseFormDialog({
+ dialogName,
+ payload: { projectId = null },
+ isOpen,
+}) {
+ return (
+ }
+ isOpen={isOpen}
+ autoFocus={true}
+ canEscapeKeyClose={true}
+ style={{ width: '400px' }}
+ >
+
+
+
+
+ );
+}
+
+export default compose(withDialogRedux())(EstimatedExpenseFormDialog);
+
+const EstimateExpenseFormDialogRoot = styled(Dialog)`
+ .bp3-dialog-body {
+ .bp3-form-group {
+ margin-bottom: 15px;
+
+ label.bp3-label {
+ margin-bottom: 3px;
+ font-size: 13px;
+ }
+ }
+ }
+ .bp3-dialog-footer {
+ padding-top: 10px;
+ }
+`;