mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
feat: add Project invoicing form dialog.
This commit is contained in:
@@ -45,6 +45,7 @@ import ProjectTaskFormDialog from '@/containers/Projects/containers/ProjectTaskF
|
|||||||
import ProjectTimeEntryFormDialog from '@/containers/Projects/containers/ProjectTimeEntryFormDialog';
|
import ProjectTimeEntryFormDialog from '@/containers/Projects/containers/ProjectTimeEntryFormDialog';
|
||||||
import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseForm';
|
import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseForm';
|
||||||
import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog';
|
import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog';
|
||||||
|
import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog';
|
||||||
|
|
||||||
import { DialogsName } from '@/constants/dialogs';
|
import { DialogsName } from '@/constants/dialogs';
|
||||||
|
|
||||||
@@ -130,6 +131,9 @@ export default function DialogsContainer() {
|
|||||||
<EstimatedExpenseFormDialog
|
<EstimatedExpenseFormDialog
|
||||||
dialogName={DialogsName.EstimateExpenseForm}
|
dialogName={DialogsName.EstimateExpenseForm}
|
||||||
/>
|
/>
|
||||||
|
<ProjectInvoicingFormDialog
|
||||||
|
dialogName={DialogsName.ProjectInvoicingForm}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,4 +43,5 @@ export enum DialogsName {
|
|||||||
ProjectTimeEntryForm = 'project-time-entry-form',
|
ProjectTimeEntryForm = 'project-time-entry-form',
|
||||||
ProjectExpenseForm = 'project-expense-form',
|
ProjectExpenseForm = 'project-expense-form',
|
||||||
EstimateExpenseForm = 'estimate-expense-form',
|
EstimateExpenseForm = 'estimate-expense-form',
|
||||||
|
ProjectInvoicingForm = 'project-invoicing-form',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ export function useExpenseFormTableColumns({ landedCost }) {
|
|||||||
disableSortBy: true,
|
disableSortBy: true,
|
||||||
width: 100,
|
width: 100,
|
||||||
},
|
},
|
||||||
...(featureCan(Features.Branches)
|
...(featureCan(Features.Projects)
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
Header: intl.get('project'),
|
Header: intl.get('project'),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export const projectTranslations = [
|
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_expense'), path: 'expense' },
|
||||||
{
|
{
|
||||||
name: intl.get('project_details.new_estimated_expense'),
|
name: intl.get('project_details.new_estimated_expense'),
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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 (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateProjectInvoicingFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={ProjectInvoicingFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ProjectInvoicingForm);
|
||||||
@@ -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 (
|
||||||
|
<Form>
|
||||||
|
<ProjectInvoicingFormFields />
|
||||||
|
<ProjectInvoicingFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<ProjectInvoicingFormProvider dialogName={dialogName}>
|
||||||
|
<ProjectInvoicingForm />
|
||||||
|
</ProjectInvoicingFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/*------------ Date -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('project_invoicing.dialog.bill_to')}
|
||||||
|
name={'date'}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<FDateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
name="date"
|
||||||
|
formatDate={(date) => date.toLocaleString()}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<FFormGroup name={'time'}>
|
||||||
|
{/*------------ All time entreis -----------*/}
|
||||||
|
<FCheckbox
|
||||||
|
name="time"
|
||||||
|
label={intl.get('project_invoicing.dialog.all_time_entries')}
|
||||||
|
/>
|
||||||
|
{/*------------ All unbilled expenses -----------*/}
|
||||||
|
<FCheckbox
|
||||||
|
name="unbilled"
|
||||||
|
label={intl.get('project_invoicing.dialog.all_unbilled_expenses')}
|
||||||
|
/>
|
||||||
|
{/*------------ All bills. -----------*/}
|
||||||
|
<FCheckbox
|
||||||
|
name="bills"
|
||||||
|
label={intl.get('project_invoicing.dialog.all_bills')}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectInvoicingFormFields;
|
||||||
@@ -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 (
|
||||||
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
<T id={'project_invoicing.label.add'} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ProjectInvoicingFormFloatingActions);
|
||||||
@@ -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 (
|
||||||
|
<DialogContent isLoading={false}>
|
||||||
|
<ProjectInvoicingFormContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useProjectInvoicingFormContext = () =>
|
||||||
|
React.useContext(ProjectInvoicingFormContext);
|
||||||
|
|
||||||
|
export { ProjectInvoicingFormProvider, useProjectInvoicingFormContext };
|
||||||
@@ -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 (
|
||||||
|
<ProjectInvoicingFormDialogRoot
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'project_invoicing.dialog.project_invoicing'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
autoFocus={true}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
style={{ width: '370px' }}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<ProjectInvoicingDialogContent dialogName={dialogName} />
|
||||||
|
</DialogSuspense>
|
||||||
|
</ProjectInvoicingFormDialogRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -174,7 +174,7 @@ export const useProjectsListColumns = () => {
|
|||||||
id: 'status',
|
id: 'status',
|
||||||
Header: '',
|
Header: '',
|
||||||
accessor: StatusAccessor,
|
accessor: StatusAccessor,
|
||||||
width: 50,
|
width: 40,
|
||||||
className: 'status',
|
className: 'status',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2108,6 +2108,7 @@
|
|||||||
"project_details.label.purchases": "Purchases",
|
"project_details.label.purchases": "Purchases",
|
||||||
"project_details.label.sales": "Sales",
|
"project_details.label.sales": "Sales",
|
||||||
"project_details.label.journals": "Journals",
|
"project_details.label.journals": "Journals",
|
||||||
|
"project_details.new_invoicing": "New Invoicing",
|
||||||
"project_details.new_expense": "New Expense",
|
"project_details.new_expense": "New Expense",
|
||||||
"project_details.new_estimated_expense": "New Estimated Expense",
|
"project_details.new_estimated_expense": "New Estimated Expense",
|
||||||
"timesheets.action.delete_timesheet": "Delete",
|
"timesheets.action.delete_timesheet": "Delete",
|
||||||
@@ -2191,8 +2192,8 @@
|
|||||||
"bill.project_name.label": "Project Name",
|
"bill.project_name.label": "Project Name",
|
||||||
"payment_receive.project_name.label": "Project Name",
|
"payment_receive.project_name.label": "Project Name",
|
||||||
"select_project": "Select project",
|
"select_project": "Select project",
|
||||||
"project":"Project",
|
"project": "Project",
|
||||||
"projects_multi_select.label":"Projects",
|
"projects_multi_select.label": "Projects",
|
||||||
"projects_multi_select.placeholder": "Filter by projects…",
|
"projects_multi_select.placeholder": "Filter by projects…",
|
||||||
"project_profitability_summary": "Project Profitability Summary",
|
"project_profitability_summary": "Project Profitability Summary",
|
||||||
"project_profitability_summary.filter_projects.all_projects": "All Projects",
|
"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.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": "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_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"
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user