From c75f46d8a44a5f0ca0e165e4740612c60f45d301 Mon Sep 17 00:00:00 2001
From: elforjani13 <39470382+elforjani13@users.noreply.github.com>
Date: Thu, 8 Sep 2022 21:43:34 +0200
Subject: [PATCH] feat: add Project invoicing form dialog.
---
src/components/DialogsContainer.tsx | 4 ++
src/constants/dialogs.ts | 1 +
.../Expenses/ExpenseForm/components.tsx | 2 +-
.../containers/ProjectDetails/common.ts | 1 +
.../ProjectInvoicingForm.schema.tsx | 13 ++++
.../ProjectInvoicingForm.tsx | 59 ++++++++++++++++++
.../ProjectInvoicingFormContent.tsx | 17 ++++++
.../ProjectInvoicingFormDialogContent.tsx | 19 ++++++
.../ProjectInvoicingFormFields.tsx | 60 +++++++++++++++++++
.../ProjectInvoicingFormFloatingActions.tsx | 47 +++++++++++++++
.../ProjectInvoicingFormProvider.tsx | 30 ++++++++++
.../ProjectInvoicingFormDialog/index.tsx | 54 +++++++++++++++++
.../containers/ProjectsLanding/components.tsx | 2 +-
src/lang/en/index.json | 13 +++-
14 files changed, 317 insertions(+), 5 deletions(-)
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.schema.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingForm.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormContent.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormDialogContent.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFields.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormFloatingActions.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/ProjectInvoicingFormProvider.tsx
create mode 100644 src/containers/Projects/containers/ProjectInvoicingFormDialog/index.tsx
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