diff --git a/src/components/DialogsContainer.tsx b/src/components/DialogsContainer.tsx index 39b363c7f..f4e1e58e5 100644 --- a/src/components/DialogsContainer.tsx +++ b/src/components/DialogsContainer.tsx @@ -45,6 +45,7 @@ import ProjectTaskFormDialog from '@/containers/Projects/containers/ProjectTaskF import ProjectTimeEntryFormDialog from '@/containers/Projects/containers/ProjectTimeEntryFormDialog'; import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseForm'; import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog'; +import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog'; import { DialogsName } from '@/constants/dialogs'; @@ -130,6 +131,9 @@ export default function DialogsContainer() { + ); } diff --git a/src/constants/dialogs.ts b/src/constants/dialogs.ts index 01b34d594..f3e97b41d 100644 --- a/src/constants/dialogs.ts +++ b/src/constants/dialogs.ts @@ -43,4 +43,5 @@ export enum DialogsName { ProjectTimeEntryForm = 'project-time-entry-form', ProjectExpenseForm = 'project-expense-form', EstimateExpenseForm = 'estimate-expense-form', + ProjectInvoicingForm = 'project-invoicing-form', } diff --git a/src/containers/Expenses/ExpenseForm/components.tsx b/src/containers/Expenses/ExpenseForm/components.tsx index b19879782..15465e3d4 100644 --- a/src/containers/Expenses/ExpenseForm/components.tsx +++ b/src/containers/Expenses/ExpenseForm/components.tsx @@ -121,7 +121,7 @@ export function useExpenseFormTableColumns({ landedCost }) { disableSortBy: true, width: 100, }, - ...(featureCan(Features.Branches) + ...(featureCan(Features.Projects) ? [ { Header: intl.get('project'), diff --git a/src/containers/Projects/containers/ProjectDetails/common.ts b/src/containers/Projects/containers/ProjectDetails/common.ts index d734ddbcb..432d142fe 100644 --- a/src/containers/Projects/containers/ProjectDetails/common.ts +++ b/src/containers/Projects/containers/ProjectDetails/common.ts @@ -1,6 +1,7 @@ import intl from 'react-intl-universal'; export const projectTranslations = [ + { name: intl.get('project_details.new_invoicing'), path: 'invoincing' }, { name: intl.get('project_details.new_expense'), path: 'expense' }, { name: intl.get('project_details.new_estimated_expense'), diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.schema.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.schema.tsx new file mode 100644 index 000000000..cd7d6e354 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.schema.tsx @@ -0,0 +1,13 @@ +import * as Yup from 'yup'; +import intl from 'react-intl-universal'; + +const Schema = Yup.object().shape({ + date: Yup.date() + .label(intl.get('project_invocing.schema.label.date')) + .required(), + time: Yup.boolean(), + unbilled: Yup.boolean(), + bills: Yup.boolean(), +}); + +export const CreateProjectInvoicingFormSchema = Schema; diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.tsx new file mode 100644 index 000000000..4e854c417 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import moment from 'moment'; +import intl from 'react-intl-universal'; +import { Formik } from 'formik'; +import { Intent } from '@blueprintjs/core'; +import { AppToaster } from '@/components'; +import ProjectInvoicingFormContent from './ProjectInvoicingFormContent'; +import { CreateProjectInvoicingFormSchema } from './ProjectInvoicingForm.schema'; + +import withDialogActions from '@/containers/Dialog/withDialogActions'; + +import { compose } from '@/utils'; + +const defaultInitialValues = { + date: moment(new Date()).format('YYYY-MM-DD'), + time: false, + unbilled: false, + bills: false, +}; + +/** + * project invoicing form. + * @returns + */ +function ProjectInvoicingForm({ + // #withDialogActions + closeDialog, +}) { + // Initial form values + const initialValues = { + ...defaultInitialValues, + }; + + // Handles the form submit. + const handleFormSubmit = (values, { setSubmitting, setErrors }) => { + // Handle request response success. + const onSuccess = (response) => {}; + + // Handle request response errors. + const onError = ({ + response: { + data: { errors }, + }, + }) => { + setSubmitting(false); + }; + }; + + return ( + + ); +} + +export default compose(withDialogActions)(ProjectInvoicingForm); diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormContent.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormContent.tsx new file mode 100644 index 000000000..56d344e29 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormContent.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Form } from 'formik'; + +import ProjectInvoicingFormFields from './ProjectInvoicingFormFields'; +import ProjectInvoicingFormFloatingActions from './ProjectInvoicingFormFloatingActions'; + +/** + * Project Invoicing form content. + */ +export default function ProjectInvoicingFormContent() { + return ( +
+ + + + ); +} diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormDialogContent.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormDialogContent.tsx new file mode 100644 index 000000000..430976741 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormDialogContent.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +import { ProjectInvoicingFormProvider } from './ProjectInvoicingFormProvider'; +import ProjectInvoicingForm from './ProjectInvoicingForm'; + +/** + * Project Invoicing form dialog content. + * @returns + */ +export default function ProjectInvoicingFormDialogContent({ + // #ownProps + dialogName, +}) { + return ( + + + + ); +} diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFields.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFields.tsx new file mode 100644 index 000000000..fbff97bd0 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFields.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import intl from 'react-intl-universal'; + +import classNames from 'classnames'; +import { CLASSES } from '@/constants/classes'; +import { Classes, Position, FormGroup, ControlGroup } from '@blueprintjs/core'; +import { + FFormGroup, + FCheckbox, + FDateInput, + FieldRequiredHint, +} from '@/components'; +import { momentFormatter } from '@/utils'; + +/** + * Project invoicing form fields. + * @returns + */ +function ProjectInvoicingFormFields() { + return ( +
+ {/*------------ Date -----------*/} + + date.toLocaleString()} + popoverProps={{ + position: Position.BOTTOM, + minimal: true, + }} + /> + + + + {/*------------ All time entreis -----------*/} + + {/*------------ All unbilled expenses -----------*/} + + {/*------------ All bills. -----------*/} + + +
+ ); +} + +export default ProjectInvoicingFormFields; diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFloatingActions.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFloatingActions.tsx new file mode 100644 index 000000000..3edbe195a --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFloatingActions.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { useFormikContext } from 'formik'; +import { Intent, Button, Classes } from '@blueprintjs/core'; +import { FormattedMessage as T } from '@/components'; +import { useProjectInvoicingFormContext } from './ProjectInvoicingFormProvider'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { compose } from '@/utils'; + +/** + * Project invoicing from floating actions + * @returns + */ +function ProjectInvoicingFormFloatingActions({ + // #withDialogActions + closeDialog, +}) { + // Formik context. + const { isSubmitting } = useFormikContext(); + + // project invoicing form dialog context. + const { dialogName } = useProjectInvoicingFormContext(); + + // Handle close button click. + const handleCancelBtnClick = () => { + closeDialog(dialogName); + }; + + return ( +
+
+ + +
+
+ ); +} + +export default compose(withDialogActions)(ProjectInvoicingFormFloatingActions); diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormProvider.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormProvider.tsx new file mode 100644 index 000000000..8e0b65d7f --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormProvider.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { DialogContent } from '@/components'; + +const ProjectInvoicingFormContext = React.createContext(); + +/** + * Project invoicing form provider. + * @returns + */ +function ProjectInvoicingFormProvider({ + // #ownProps + dialogName, + ...props +}) { + // State provider. + const provider = { + dialogName, + }; + + return ( + + + + ); +} + +const useProjectInvoicingFormContext = () => + React.useContext(ProjectInvoicingFormContext); + +export { ProjectInvoicingFormProvider, useProjectInvoicingFormContext }; diff --git a/src/containers/Projects/containers/ProjectInvoicingFormDialog/index.tsx b/src/containers/Projects/containers/ProjectInvoicingFormDialog/index.tsx new file mode 100644 index 000000000..085ef01d9 --- /dev/null +++ b/src/containers/Projects/containers/ProjectInvoicingFormDialog/index.tsx @@ -0,0 +1,54 @@ +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 ProjectInvoicingDialogContent = React.lazy( + () => import('./ProjectInvoicingFormDialogContent'), +); + +/** + * Project invoicing form dialog. + * @returns + */ +function ProjectInvoicingFormDialog({ dialogName, payload: {}, isOpen }) { + + return ( + } + isOpen={isOpen} + autoFocus={true} + canEscapeKeyClose={true} + style={{ width: '370px' }} + > + + + + + ); +} + +export default compose(withDialogRedux())(ProjectInvoicingFormDialog); + +const ProjectInvoicingFormDialogRoot = styled(Dialog)` + .bp3-dialog-body { + .bp3-form-group { + margin-bottom: 15px; + margin-top: 15px; + + label.bp3-label { + margin-bottom: 3px; + font-size: 13px; + } + } + + label.bp3-control.bp3-checkbox { + margin-top: 15px; + } + } + .bp3-dialog-footer { + padding-top: 10px; + } +`; diff --git a/src/containers/Projects/containers/ProjectsLanding/components.tsx b/src/containers/Projects/containers/ProjectsLanding/components.tsx index 1d0b49ccc..77dedf738 100644 --- a/src/containers/Projects/containers/ProjectsLanding/components.tsx +++ b/src/containers/Projects/containers/ProjectsLanding/components.tsx @@ -174,7 +174,7 @@ export const useProjectsListColumns = () => { id: 'status', Header: '', accessor: StatusAccessor, - width: 50, + width: 40, className: 'status', }, ], diff --git a/src/lang/en/index.json b/src/lang/en/index.json index 1cab693f9..73205b5c3 100644 --- a/src/lang/en/index.json +++ b/src/lang/en/index.json @@ -2108,6 +2108,7 @@ "project_details.label.purchases": "Purchases", "project_details.label.sales": "Sales", "project_details.label.journals": "Journals", + "project_details.new_invoicing": "New Invoicing", "project_details.new_expense": "New Expense", "project_details.new_estimated_expense": "New Estimated Expense", "timesheets.action.delete_timesheet": "Delete", @@ -2191,8 +2192,8 @@ "bill.project_name.label": "Project Name", "payment_receive.project_name.label": "Project Name", "select_project": "Select project", - "project":"Project", - "projects_multi_select.label":"Projects", + "project": "Project", + "projects_multi_select.label": "Projects", "projects_multi_select.placeholder": "Filter by projects…", "project_profitability_summary": "Project Profitability Summary", "project_profitability_summary.filter_projects.all_projects": "All Projects", @@ -2201,5 +2202,11 @@ "project_profitability_summary.filter_projects.without_zero_balance.hint": "Include projects that onces have transactions on the given date period only.", "project_profitability_summary.filter_projects.with_transactions": "Projects with transactions", "project_profitability_summary.filter_projects.with_transactions.hint": "Include projects that onces have transactions on the given date period only.", - "project_profitability_summary.filter_options.label": "Filter projects" + "project_profitability_summary.filter_options.label": "Filter projects", + "project_invoicing.label.add": "Add", + "project_invoicing.dialog.project_invoicing": "Project Invoicing", + "project_invoicing.dialog.all_time_entries": "All time entries", + "project_invoicing.dialog.all_unbilled_expenses": "All unbilled expenses", + "project_invoicing.dialog.all_bills": "All bills", + "project_invoicing.dialog.bill_to": "Bill To" } \ No newline at end of file