mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +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 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() {
|
||||
<EstimatedExpenseFormDialog
|
||||
dialogName={DialogsName.EstimateExpenseForm}
|
||||
/>
|
||||
<ProjectInvoicingFormDialog
|
||||
dialogName={DialogsName.ProjectInvoicingForm}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,4 +43,5 @@ export enum DialogsName {
|
||||
ProjectTimeEntryForm = 'project-time-entry-form',
|
||||
ProjectExpenseForm = 'project-expense-form',
|
||||
EstimateExpenseForm = 'estimate-expense-form',
|
||||
ProjectInvoicingForm = 'project-invoicing-form',
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ export function useExpenseFormTableColumns({ landedCost }) {
|
||||
disableSortBy: true,
|
||||
width: 100,
|
||||
},
|
||||
...(featureCan(Features.Branches)
|
||||
...(featureCan(Features.Projects)
|
||||
? [
|
||||
{
|
||||
Header: intl.get('project'),
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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',
|
||||
Header: '',
|
||||
accessor: StatusAccessor,
|
||||
width: 50,
|
||||
width: 40,
|
||||
className: 'status',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user