diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.schema.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.schema.tsx new file mode 100644 index 000000000..c2bc038cc --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.schema.tsx @@ -0,0 +1,21 @@ +import * as Yup from 'yup'; +import intl from 'react-intl-universal'; +import { DATATYPES_LENGTH } from 'common/dataTypes'; + +const Schema = Yup.object().shape({ + expenseName: Yup.string().label( + intl.get('expense.schema.label.expense_name'), + ), + estimatedExpense: Yup.number().label( + intl.get('expense.schema.label.estimated_expense'), + ), + expemseDate: Yup.date(), + expenseQuantity: Yup.number().label(intl.get('expense.schema.label.quantity')), + expenseUnitPrice: Yup.number().label( + intl.get('expense.schema.label.unitPrice'), + ), + expenseTotal: Yup.number(), + expenseCharge: Yup.string(), +}); + +export const CreateExpenseFormSchema = Schema; diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.tsx new file mode 100644 index 000000000..6bcf46531 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseForm.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import moment from 'moment'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { AppToaster } from 'components'; +import { CreateExpenseFormSchema } from './ExpenseForm.schema'; +import ExpenseFormContent from './ExpenseFormContent'; +import { useExpenseFormContext } from './ExpenseFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; + +import { compose } from 'utils'; + +const defaultInitialValues = { + expenseName: '', + estimatedExpense: '', + expemseDate: moment(new Date()).format('YYYY-MM-DD'), + expenseUnitPrice: '', + expenseQuantity: 1, + expenseCharge: '% markup', + percentage: '', + expenseTotal: '', +}; + +/** + * Expense form. + * @returns + */ +function ExpenseForm({ + //#withDialogActions + closeDialog, +}) { + const initialValues = { + ...defaultInitialValues, + }; + + // Handles the form submit. + const handleFormSubmit = (values, { setSubmitting, setErrors }) => { + const form = {}; + + // 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)(ExpenseForm); diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormChargeFields.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormChargeFields.tsx new file mode 100644 index 000000000..9f8272592 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormChargeFields.tsx @@ -0,0 +1,55 @@ +//@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 ( + + + + + + + + + ); +} + +/** + * Expense form charge fields. + * @returns + */ +export default function ExpenseFormChargeFields() { + const { values } = useFormikContext(); + + return ( + + + + + + + + + ); +} diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormContent.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormContent.tsx new file mode 100644 index 000000000..a73c7d1dd --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormContent.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Form } from 'formik'; +import ExpenseFormFields from './ExpenseFormFields'; +import ExpneseFormFloatingActions from './ExpneseFormFloatingActions'; + +/** + * Expense form content. + * @returns + */ +export default function ExpenseFormContent() { + return ( +
+ + + + ); +} diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormDialogContent.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormDialogContent.tsx new file mode 100644 index 000000000..12a9f01b9 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormDialogContent.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { ExpenseFormProvider } from './ExpenseFormProvider'; +import ExpenseForm from './ExpenseForm'; + +/** + * Expense form dialog content. + * @returns + */ +export default function ExpenseFormDialogContent({ + // #ownProps + dialogName, + expense, +}) { + return ( + + + + ); +} diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormFields.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormFields.tsx new file mode 100644 index 000000000..07901a049 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormFields.tsx @@ -0,0 +1,141 @@ +//@ts-nocheck +import React from 'react'; +import styled from 'styled-components'; +import intl from 'react-intl-universal'; +import { Classes, Position, ControlGroup } from '@blueprintjs/core'; +import { CLASSES } from 'common/classes'; +import classNames from 'classnames'; +import { + FFormGroup, + FInputGroup, + FDateInput, + FormattedMessage as T, +} from 'components'; +import { ExpenseSelect } from '../../components'; +import ExpenseFormChargeFields from './ExpenseFormChargeFields'; +import { momentFormatter } from 'utils'; +import { useExpenseFormContext } from './ExpenseFormProvider'; +import { ChargeSelect } from '../../components'; +import { expenseChargeOption } from 'common/modalChargeOptions'; + +/** + * Expense form fields. + * @returns + */ +export default function ExpenseFormFields() { + return ( +
+ {/*------------ Expense Name -----------*/} + + + + {/*------------ Track to Expense -----------*/} + + + + + {/*------------ Extimated Date -----------*/} + + date.toLocaleString()} + popoverProps={{ + position: Position.BOTTOM, + minimal: true, + }} + /> + + {/*------------ Quantity -----------*/} + + + + + Cost to you + {/*------------ Unit Price -----------*/} + + + + + + + + + + What you'll charge + {/*------------ Charge -----------*/} + } + className={classNames('form-group--select-list', Classes.FILL)} + > + + + + {/*------------ Charge Fields -----------*/} + + + {/*------------ Total -----------*/} + + + + + 0.00 + +
+ ); +} + +const MetaLineLabel = styled.div` + font-size: 14px; + line-height: 1.5rem; + font-weight: 500; + margin-bottom: 8px; +`; + +const ExpenseTotalBase = styled.div` + display: block; + text-align: right; +`; + +const ExpenseTotalLabel = styled.div` + font-size: 14px; + line-height: 1.5rem; + opacity: 0.75; +`; + +const ExpenseTotal = styled.div` + font-size: 15px; + font-weight: 700; + padding-left: 14px; + line-height: 2rem; +`; diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormProvider.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormProvider.tsx new file mode 100644 index 000000000..eea3375a4 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpenseFormProvider.tsx @@ -0,0 +1,30 @@ +//@ts-nocheck +import React from 'react'; +import { DialogContent } from 'components'; + +const ExpenseFormContext = React.createContext(); + +/** + * Expense form provider. + * @returns + */ +function ExpenseFormProvider({ + //#OwnProps + dialogName, + expenseId, + ...props +}) { + // state provider. + const provider = { + dialogName, + }; + + return ( + + + + ); +} + +const useExpenseFormContext = () => React.useContext(ExpenseFormContext); +export { ExpenseFormProvider, useExpenseFormContext }; diff --git a/src/containers/Projects/containers/ExpenseFormDialog/ExpneseFormFloatingActions.tsx b/src/containers/Projects/containers/ExpenseFormDialog/ExpneseFormFloatingActions.tsx new file mode 100644 index 000000000..ad056b14b --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/ExpneseFormFloatingActions.tsx @@ -0,0 +1,44 @@ +//@ts-nocheck +import React from 'react'; +import { useFormikContext } from 'formik'; +import { Intent, Button, Classes } from '@blueprintjs/core'; +import { FormattedMessage as T } from 'components'; +import { useExpenseFormContext } from './ExpenseFormProvider'; +import withDialogActions from 'containers/Dialog/withDialogActions'; +import { compose } from 'utils'; + +function ExpneseFormFloatingActions({ + // #withDialogActions + closeDialog, +}) { + // Formik context. + const { isSubmitting } = useFormikContext(); + + // expense form dialog context. + const { dialogName } = useExpenseFormContext(); + + // Handle close button click. + const handleCancelBtnClick = () => { + closeDialog(dialogName); + }; + + return ( +
+
+ + +
+
+ ); +} + +export default compose(withDialogActions)(ExpneseFormFloatingActions); diff --git a/src/containers/Projects/containers/ExpenseFormDialog/index.tsx b/src/containers/Projects/containers/ExpenseFormDialog/index.tsx new file mode 100644 index 000000000..fbcd6d079 --- /dev/null +++ b/src/containers/Projects/containers/ExpenseFormDialog/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 ExpenseFormeDialogContent = React.lazy( + () => import('./ExpenseFormDialogContent'), +); + +/** + * Expense form dialog. + * @returns + */ +function ExpenseFormDialog({ + dialogName, + payload: { projectId = null }, + isOpen, +}) { + return ( + } + isOpen={isOpen} + autoFocus={true} + canEscapeKeyClose={true} + style={{ width: '400px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(ExpenseFormDialog); + +const ExpenseFormDialogRoot = 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; + } +`;