mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
Compare commits
12 Commits
@bigcapita
...
BIG-386-li
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
754618aa7a | ||
|
|
50c905eabb | ||
|
|
709e06a646 | ||
|
|
8826d2bc5b | ||
|
|
38a961b899 | ||
|
|
7ef7e126e5 | ||
|
|
bcf0ec25b8 | ||
|
|
965a8966f6 | ||
|
|
b030d6ea37 | ||
|
|
31fef21362 | ||
|
|
6f2a456a56 | ||
|
|
6134ad5598 |
@@ -1,7 +0,0 @@
|
|||||||
import intl from 'react-intl-universal';
|
|
||||||
|
|
||||||
export const modalChargeOptions = [
|
|
||||||
{ name: 'Hourly rate', value: 'Hourly rate' },
|
|
||||||
{ name: 'Fixed price', value: 'Fixed price' },
|
|
||||||
{ name: 'Non-chargeable', value: 'Non-chargeable' },
|
|
||||||
];
|
|
||||||
@@ -19,6 +19,8 @@ export const TABLES = {
|
|||||||
WAREHOUSE_TRANSFERS: 'warehouse_transfers',
|
WAREHOUSE_TRANSFERS: 'warehouse_transfers',
|
||||||
PROJECTS: 'projects',
|
PROJECTS: 'projects',
|
||||||
TIMESHEETS: 'timesheets',
|
TIMESHEETS: 'timesheets',
|
||||||
|
PURCHASES: 'purchases',
|
||||||
|
SALES: 'sales',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TABLE_SIZE = {
|
export const TABLE_SIZE = {
|
||||||
|
|||||||
@@ -88,23 +88,27 @@ export default function TableHeader() {
|
|||||||
},
|
},
|
||||||
} = useContext(TableContext);
|
} = useContext(TableContext);
|
||||||
|
|
||||||
|
// Can't contiunue if the thead is disabled.
|
||||||
|
if (hideTableHeader) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (headerLoading && TableHeaderSkeletonRenderer) {
|
if (headerLoading && TableHeaderSkeletonRenderer) {
|
||||||
return <TableHeaderSkeletonRenderer />;
|
return <TableHeaderSkeletonRenderer />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!hideTableHeader && (
|
<ScrollSyncPane>
|
||||||
<ScrollSyncPane>
|
<div className="thead">
|
||||||
<div className="thead">
|
<div className={'thead-inner'}>
|
||||||
<div className={'thead-inner'}>
|
{headerGroups.map((headerGroup, index) => (
|
||||||
{headerGroups.map((headerGroup, index) => (
|
<TableHeaderGroup key={index} headerGroup={headerGroup} />
|
||||||
<TableHeaderGroup key={index} headerGroup={headerGroup} />
|
))}
|
||||||
))}
|
<If condition={progressBarLoading}>
|
||||||
<If condition={progressBarLoading}>
|
<MaterialProgressBar />
|
||||||
<MaterialProgressBar />
|
</If>
|
||||||
</If>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</ScrollSyncPane>
|
</div>
|
||||||
)
|
</ScrollSyncPane>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,10 @@ import WarehouseActivateDialog from '../containers/Dialogs/WarehouseActivateDial
|
|||||||
import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog';
|
import CustomerOpeningBalanceDialog from '../containers/Dialogs/CustomerOpeningBalanceDialog';
|
||||||
import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog';
|
import VendorOpeningBalanceDialog from '../containers/Dialogs/VendorOpeningBalanceDialog';
|
||||||
import ProjectFormDialog from '../containers/Projects/containers/ProjectFormDialog';
|
import ProjectFormDialog from '../containers/Projects/containers/ProjectFormDialog';
|
||||||
import TaskFormDialog from '../containers/Projects/containers/TaskFormDialog';
|
import ProjectTaskFormDialog from '../containers/Projects/containers/ProjectTaskFormDialog';
|
||||||
import TimeEntryFormDialog from '../containers/Projects/containers/TimeEntryFormDialog';
|
import ProjectTimeEntryFormDialog from '../containers/Projects/containers/ProjectTimeEntryFormDialog';
|
||||||
|
import ProjectExpenseForm from '../containers/Projects/containers/ProjectExpenseForm';
|
||||||
|
import EstimatedExpenseFormDialog from '../containers/Projects/containers/EstimatedExpenseFormDialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dialogs container.
|
* Dialogs container.
|
||||||
@@ -94,8 +96,10 @@ export default function DialogsContainer() {
|
|||||||
<CustomerOpeningBalanceDialog dialogName={'customer-opening-balance'} />
|
<CustomerOpeningBalanceDialog dialogName={'customer-opening-balance'} />
|
||||||
<VendorOpeningBalanceDialog dialogName={'vendor-opening-balance'} />
|
<VendorOpeningBalanceDialog dialogName={'vendor-opening-balance'} />
|
||||||
<ProjectFormDialog dialogName={'project-form'} />
|
<ProjectFormDialog dialogName={'project-form'} />
|
||||||
<TaskFormDialog dialogName={'task-form'} />
|
<ProjectTaskFormDialog dialogName={'project-task-form'} />
|
||||||
<TimeEntryFormDialog dialogName={'time-entry-form'} />
|
<ProjectTimeEntryFormDialog dialogName={'project-time-entry-form'} />
|
||||||
|
<ProjectExpenseForm dialogName={'project-expense-form'} />
|
||||||
|
<EstimatedExpenseFormDialog dialogName={'estimated-expense-form'} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import TransactionsLockingAlerts from '../TransactionsLocking/TransactionsLockin
|
|||||||
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
|
import WarehousesAlerts from '../Preferences/Warehouses/WarehousesAlerts';
|
||||||
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts';
|
import WarehousesTransfersAlerts from '../WarehouseTransfers/WarehousesTransfersAlerts';
|
||||||
import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
|
import BranchesAlerts from '../Preferences/Branches/BranchesAlerts';
|
||||||
|
import ProjectAlerts from '../../containers/Projects/containers/ProjectAlerts';
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
...AccountsAlerts,
|
...AccountsAlerts,
|
||||||
@@ -50,4 +51,5 @@ export default [
|
|||||||
...WarehousesAlerts,
|
...WarehousesAlerts,
|
||||||
...WarehousesTransfersAlerts,
|
...WarehousesTransfersAlerts,
|
||||||
...BranchesAlerts,
|
...BranchesAlerts,
|
||||||
|
...ProjectAlerts,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
import { FSelect } from 'components';
|
import { FSelect } from '../../../components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -8,7 +8,7 @@ import { FSelect } from 'components';
|
|||||||
* @param {*} param1
|
* @param {*} param1
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const taskModalChargeRenderer = (item, { handleClick, modifiers, query }) => {
|
const chargeTypeItemRenderer = (item, { handleClick, modifiers, query }) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
label={item.label}
|
label={item.label}
|
||||||
@@ -19,8 +19,8 @@ const taskModalChargeRenderer = (item, { handleClick, modifiers, query }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const taskModalChargeSelectProps = {
|
const chargeTypeSelectProps = {
|
||||||
itemRenderer: taskModalChargeRenderer,
|
itemRenderer: chargeTypeItemRenderer,
|
||||||
valueAccessor: 'value',
|
valueAccessor: 'value',
|
||||||
labelAccessor: 'name',
|
labelAccessor: 'name',
|
||||||
};
|
};
|
||||||
@@ -30,22 +30,21 @@ const taskModalChargeSelectProps = {
|
|||||||
* @param param0
|
* @param param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function TaskModalChargeSelect({ items, ...rest }) {
|
export function ChangeTypesSelect({ items, ...rest }) {
|
||||||
return (
|
return (
|
||||||
<FSelect
|
<FSelect
|
||||||
{...taskModalChargeSelectProps}
|
{...chargeTypeSelectProps}
|
||||||
{...rest}
|
{...rest}
|
||||||
items={items}
|
items={items}
|
||||||
input={TaskModalChargeSelectButton}
|
input={ChargeTypeSelectButton}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param param0
|
* @param param0
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TaskModalChargeSelectButton({ label }) {
|
function ChargeTypeSelectButton({ label }) {
|
||||||
return <Button text={label} />;
|
return <Button text={label} />;
|
||||||
}
|
}
|
||||||
67
src/containers/Projects/components/ExpenseSelect.tsx
Normal file
67
src/containers/Projects/components/ExpenseSelect.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
|
import { FSelect } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param query
|
||||||
|
* @param expense
|
||||||
|
* @param _index
|
||||||
|
* @param exactMatch
|
||||||
|
*/
|
||||||
|
const expenseItemPredicate = (query, expense, _index, exactMatch) => {
|
||||||
|
const normalizedTitle = expense.name.toLowerCase();
|
||||||
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
|
if (exactMatch) {
|
||||||
|
return normalizedTitle === normalizedQuery;
|
||||||
|
} else {
|
||||||
|
return `${expense.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param expense
|
||||||
|
* @param param1
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const expenseItemRenderer = (expense, { handleClick, modifiers, query }) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
active={modifiers.active}
|
||||||
|
disabled={modifiers.disabled}
|
||||||
|
key={expense.id}
|
||||||
|
onClick={handleClick}
|
||||||
|
text={expense.name}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const expenseSelectProps = {
|
||||||
|
itemPredicate: expenseItemPredicate,
|
||||||
|
itemRenderer: expenseItemRenderer,
|
||||||
|
valueAccessor: 'id',
|
||||||
|
labelAccessor: 'name',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ExpenseSelect({ expenses, defaultText, ...rest }) {
|
||||||
|
return (
|
||||||
|
<FSelect
|
||||||
|
items={expenses}
|
||||||
|
{...expenseSelectProps}
|
||||||
|
{...rest}
|
||||||
|
input={ExpenseSelectButton}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ExpenseSelectButton({ label, ...rest }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
text={label ? label : intl.get('choose_an_estimated_expense')}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
20
src/containers/Projects/components/FInputGroupComponent.tsx
Normal file
20
src/containers/Projects/components/FInputGroupComponent.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { FInputGroup } from 'components';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
|
||||||
|
export function FInputGroupComponent({ toField, ...props }) {
|
||||||
|
const { values, setFieldValue } = useFormikContext();
|
||||||
|
const { expenseQuantity, expenseUnitPrice } = values;
|
||||||
|
const total = expenseQuantity * expenseUnitPrice;
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setFieldValue(toField, total);
|
||||||
|
};
|
||||||
|
|
||||||
|
const inputGroupProps = {
|
||||||
|
onBlur: handleBlur,
|
||||||
|
...props,
|
||||||
|
};
|
||||||
|
return <FInputGroup {...inputGroupProps} />;
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import { FSelect } from 'components';
|
|||||||
* @param {*} exactMatch
|
* @param {*} exactMatch
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const projectItemPredicate = (query, project, _index, exactMatch) => {
|
const projectsItemPredicate = (query, project, _index, exactMatch) => {
|
||||||
const normalizedTitle = project.name.toLowerCase();
|
const normalizedTitle = project.name.toLowerCase();
|
||||||
const normalizedQuery = query.toLowerCase();
|
const normalizedQuery = query.toLowerCase();
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ const projectItemPredicate = (query, project, _index, exactMatch) => {
|
|||||||
* @param {*} param1
|
* @param {*} param1
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const projectItemRenderer = (project, { handleClick, modifiers, query }) => {
|
const projectsItemRenderer = (project, { handleClick, modifiers, query }) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
active={modifiers.active}
|
active={modifiers.active}
|
||||||
@@ -41,13 +41,13 @@ const projectItemRenderer = (project, { handleClick, modifiers, query }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const projectSelectProps = {
|
const projectSelectProps = {
|
||||||
itemPredicate: projectItemPredicate,
|
itemPredicate: projectsItemPredicate,
|
||||||
itemRenderer: projectItemRenderer,
|
itemRenderer: projectsItemRenderer,
|
||||||
valueAccessor: 'id',
|
valueAccessor: 'id',
|
||||||
labelAccessor: 'name',
|
labelAccessor: 'name',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ProjectSelect({ projects, ...rest }) {
|
export function ProjectsSelect({ projects, ...rest }) {
|
||||||
return (
|
return (
|
||||||
<FSelect
|
<FSelect
|
||||||
items={projects}
|
items={projects}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
import { MenuItem, Button } from '@blueprintjs/core';
|
||||||
import { FSelect } from '../../../../../components/Forms';
|
import { FSelect } from 'components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
5
src/containers/Projects/components/index.ts
Normal file
5
src/containers/Projects/components/index.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export * from './ExpenseSelect';
|
||||||
|
export * from './ChangeTypesSelect';
|
||||||
|
export * from './TaskSelect';
|
||||||
|
export * from './ProjectsSelect';
|
||||||
|
export * from './FInputGroupComponent';
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as Yup from 'yup';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
|
const Schema = Yup.object().shape({
|
||||||
|
estimatedExpense: Yup.number().label(
|
||||||
|
intl.get('estimated_expense.schema.label.estimated_expense'),
|
||||||
|
),
|
||||||
|
quantity: Yup.number().label(
|
||||||
|
intl.get('estimated_expense.schema.label.quantity'),
|
||||||
|
),
|
||||||
|
unitPrice: Yup.number().label(
|
||||||
|
intl.get('estimated_expense.schema.label.unit_price'),
|
||||||
|
),
|
||||||
|
expenseTotal: Yup.number(),
|
||||||
|
charge: Yup.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateEstimatedExpenseFormSchema = Schema;
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Formik } from 'formik';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { CreateEstimatedExpenseFormSchema } from './EstimatedExpense.schema';
|
||||||
|
import EstimatedExpenseFormConent from './EstimatedExpenseFormConent';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
const defaultInitialValues = {
|
||||||
|
estimatedExpense: '',
|
||||||
|
unitPrice: '',
|
||||||
|
quantity: 1,
|
||||||
|
charge: '% markup',
|
||||||
|
percentage: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimated expense form dialog.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function EstimatedExpenseForm({
|
||||||
|
//#withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const initialValues = {
|
||||||
|
...defaultInitialValues,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handles the form submit.
|
||||||
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
|
// Handle request response success.
|
||||||
|
const onSuccess = (response) => {
|
||||||
|
AppToaster.show({});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle request response errors.
|
||||||
|
const onError = ({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {
|
||||||
|
setSubmitting(false);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateEstimatedExpenseFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={EstimatedExpenseFormConent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(EstimatedExpenseForm);
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
//@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 (
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('estimated_expenses.dialog.percentage')}
|
||||||
|
name={'percentage'}
|
||||||
|
>
|
||||||
|
<FInputGroup name="percentage" />
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomPirceField() {
|
||||||
|
return (
|
||||||
|
<ControlGroup className={Classes.FILL}>
|
||||||
|
<FFormGroup
|
||||||
|
name={'unitPrice'}
|
||||||
|
label={intl.get('estimated_expenses.dialog.unit_price')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="unitPrice" />
|
||||||
|
</FFormGroup>
|
||||||
|
<FFormGroup
|
||||||
|
name={'unitPrice'}
|
||||||
|
label={intl.get('estimated_expenses.dialog.total')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="total" />
|
||||||
|
</FFormGroup>
|
||||||
|
</ControlGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* estimate expense form charge fields.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function EstimatedExpenseFormChargeFields() {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={values.charge === 'markup'}>
|
||||||
|
<PercentageFormField />
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.When condition={values.charge === 'custom_pirce'}>
|
||||||
|
<CustomPirceField />
|
||||||
|
</Choose.When>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import EstimatedExpenseFormFields from './EstimatedExpenseFormFields';
|
||||||
|
import EstimatedExpenseFormFloatingActions from './EstimatedExpenseFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimated expense form content.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function EstimatedExpenseFormConent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<EstimatedExpenseFormFields />
|
||||||
|
<EstimatedExpenseFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { EstimatedExpenseFormProvider } from './EstimatedExpenseFormProvider';
|
||||||
|
import EstimatedExpenseForm from './EstimatedExpenseForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate expense form dialog.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
export default function EstimatedExpenseFormDialogContent({
|
||||||
|
//#ownProps
|
||||||
|
dialogName,
|
||||||
|
estimatedExpense,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<EstimatedExpenseFormProvider
|
||||||
|
dialogName={dialogName}
|
||||||
|
estimatedExpenseId={estimatedExpense}
|
||||||
|
>
|
||||||
|
<EstimatedExpenseForm />
|
||||||
|
</EstimatedExpenseFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Classes, ControlGroup } from '@blueprintjs/core';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import {
|
||||||
|
FFormGroup,
|
||||||
|
FInputGroup,
|
||||||
|
FormattedMessage as T,
|
||||||
|
FieldRequiredHint,
|
||||||
|
} from 'components';
|
||||||
|
import { ExpenseSelect, FInputGroupComponent } from '../../components';
|
||||||
|
import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
|
||||||
|
import EstimatedExpenseFormChargeFields from './EstimatedExpenseFormChargeFields';
|
||||||
|
import { ChangeTypesSelect } from '../../components';
|
||||||
|
import { expenseChargeOption } from 'containers/Projects/containers/common/modalChargeOptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimated expense form fields.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function EstimatedExpenseFormFields() {
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/*------------ Estimated Expense -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'estimatedExpense'}
|
||||||
|
label={intl.get('estimated_expenses.dialog.estimated_expense')}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<ExpenseSelect
|
||||||
|
name={'estimatedExpense'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
expenses={[]}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Quantity -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('estimated_expenses.dialog.quantity')}
|
||||||
|
name={'quantity'}
|
||||||
|
>
|
||||||
|
<FInputGroupComponent name="quantity" />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<MetaLineLabel>
|
||||||
|
<T id={'estimated_expenses.dialog.cost_to_you'} />
|
||||||
|
</MetaLineLabel>
|
||||||
|
{/*------------ Unit Price -----------*/}
|
||||||
|
<ControlGroup className={Classes.FILL}>
|
||||||
|
<FFormGroup
|
||||||
|
name={'unitPrice'}
|
||||||
|
label={intl.get('estimated_expenses.dialog.unit_price')}
|
||||||
|
>
|
||||||
|
<FInputGroupComponent name="unitPrice" />
|
||||||
|
</FFormGroup>
|
||||||
|
<FFormGroup
|
||||||
|
name={'unitPrice'}
|
||||||
|
label={intl.get('estimated_expenses.dialog.total')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="expenseTotal" />
|
||||||
|
</FFormGroup>
|
||||||
|
</ControlGroup>
|
||||||
|
|
||||||
|
<MetaLineLabel>
|
||||||
|
<T id={'estimated_expenses.dialog.what_you_ll_charge'} />
|
||||||
|
</MetaLineLabel>
|
||||||
|
{/*------------ Charge -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'charge'}
|
||||||
|
label={<T id={'estimated_expenses.dialog.charge'} />}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<ChangeTypesSelect
|
||||||
|
name="charge"
|
||||||
|
items={expenseChargeOption}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
filterable={false}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
<EstimatedExpenseFormChargeFields />
|
||||||
|
{/*------------ Estimated Amount -----------*/}
|
||||||
|
<EstimatedAmountWrap>
|
||||||
|
<EstimatedAmountLabel>
|
||||||
|
<T id={'estimated_expenses.dialog.estimated_amount'} />
|
||||||
|
</EstimatedAmountLabel>
|
||||||
|
<EstimatedAmount>0.00</EstimatedAmount>
|
||||||
|
</EstimatedAmountWrap>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const MetaLineLabel = styled.div`
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const EstimatedAmountWrap = styled.div`
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
`;
|
||||||
|
const EstimatedAmountLabel = styled.span`
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
`;
|
||||||
|
const EstimatedAmount = styled.span`
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding-left: 14px;
|
||||||
|
line-height: 2rem;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { useFormikContext } from 'formik';
|
||||||
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
|
import { FormattedMessage as T } from 'components';
|
||||||
|
import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimated expense form floating actions.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function EstimatedExpenseFormFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// expense form dialog context.
|
||||||
|
const { dialogName } = useEstimatedExpenseFormContext();
|
||||||
|
|
||||||
|
// 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: '75px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(EstimatedExpenseFormFloatingActions);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
|
const EstimatedExpenseFormContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimated expense form provider.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function EstimatedExpenseFormProvider({
|
||||||
|
//#OwnProps
|
||||||
|
dialogName,
|
||||||
|
estimatedExpenseId,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// state provider.
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<EstimatedExpenseFormContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useEstimatedExpenseFormContext = () =>
|
||||||
|
React.useContext(EstimatedExpenseFormContext);
|
||||||
|
|
||||||
|
export { EstimatedExpenseFormProvider, useEstimatedExpenseFormContext };
|
||||||
@@ -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 EstimatedExpenseFormDialogContent = React.lazy(
|
||||||
|
() => import('./EstimatedExpenseFormDialogContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimate expense form dialog.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function EstimatedExpenseFormDialog({
|
||||||
|
dialogName,
|
||||||
|
payload: { projectId = null },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<EstimateExpenseFormDialogRoot
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'estimated_expenses.dialog.label'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
autoFocus={true}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
style={{ width: '400px' }}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<EstimatedExpenseFormDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
estimatedExpense={projectId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</EstimateExpenseFormDialogRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(EstimatedExpenseFormDialog);
|
||||||
|
|
||||||
|
const EstimateExpenseFormDialogRoot = 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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { FormattedMessage as T, FormattedHTMLMessage } from 'components';
|
||||||
|
import { Intent, Alert } from '@blueprintjs/core';
|
||||||
|
import { AppToaster } from 'components';
|
||||||
|
import { useDeleteProject } from '../../hooks';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from 'containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from 'containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project delete alert.
|
||||||
|
*/
|
||||||
|
function ProjectDeleteAlert({
|
||||||
|
name,
|
||||||
|
|
||||||
|
// #withAlertStoreConnect
|
||||||
|
isOpen,
|
||||||
|
payload: { projectId },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
|
||||||
|
// #withDrawerActions
|
||||||
|
closeDrawer,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: deleteProjectMutate, isLoading } = useDeleteProject();
|
||||||
|
|
||||||
|
// handle cancel delete project alert.
|
||||||
|
const handleCancelDeleteAlert = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handleConfirm delete project
|
||||||
|
const handleConfirmProjectDelete = () => {
|
||||||
|
deleteProjectMutate(projectId)
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('projects.alert.delete_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'delete'} />}
|
||||||
|
icon="trash"
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelDeleteAlert}
|
||||||
|
onConfirm={handleConfirmProjectDelete}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<FormattedHTMLMessage id={'projects.alert.once_delete_this_project'} />
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ProjectDeleteAlert);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const ProjectDeleteAlert = React.lazy(() => import('./ProjectDeleteAlert'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project alerts.
|
||||||
|
*/
|
||||||
|
export default [{ name: 'project-delete', component: ProjectDeleteAlert }];
|
||||||
@@ -14,10 +14,11 @@ import {
|
|||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
DashboardRowsHeightButton,
|
DashboardRowsHeightButton,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { TransactionSelect } from './components';
|
import { ProjectTransactionsSelect } from './components';
|
||||||
import withSettings from '../../../Settings/withSettings';
|
import withSettings from '../../../Settings/withSettings';
|
||||||
import withSettingsActions from '../../../Settings/withSettingsActions';
|
import withSettingsActions from '../../../Settings/withSettingsActions';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { projectTranslations } from './common';
|
||||||
import { useProjectDetailContext } from './ProjectDetailProvider';
|
import { useProjectDetailContext } from './ProjectDetailProvider';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -40,7 +41,13 @@ function ProjectDetailActionsBar({
|
|||||||
|
|
||||||
// Handle new transaction button click.
|
// Handle new transaction button click.
|
||||||
const handleNewTransactionBtnClick = ({ path }) => {
|
const handleNewTransactionBtnClick = ({ path }) => {
|
||||||
history.push(`/${path}`);
|
switch (path) {
|
||||||
|
case 'expense':
|
||||||
|
openDialog('project-expense-form', { projectId });
|
||||||
|
break;
|
||||||
|
case 'estimated_expense':
|
||||||
|
openDialog('estimated-expense-form', { projectId });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditProjectBtnClick = () => {
|
const handleEditProjectBtnClick = () => {
|
||||||
@@ -50,11 +57,13 @@ function ProjectDetailActionsBar({
|
|||||||
};
|
};
|
||||||
// Handle table row size change.
|
// Handle table row size change.
|
||||||
const handleTableRowSizeChange = (size) => {
|
const handleTableRowSizeChange = (size) => {
|
||||||
addSetting('timesheets', 'tableSize', size);
|
addSetting('timesheets', 'tableSize', size) &&
|
||||||
|
addSetting('sales', 'tableSize', size) &&
|
||||||
|
addSetting('purchases', 'tableSize', size);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTimeEntryBtnClick = () => {
|
const handleTimeEntryBtnClick = () => {
|
||||||
openDialog('time-entry-form', {
|
openDialog('project-time-entry-form', {
|
||||||
projectId,
|
projectId,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -65,11 +74,8 @@ function ProjectDetailActionsBar({
|
|||||||
return (
|
return (
|
||||||
<DashboardActionsBar>
|
<DashboardActionsBar>
|
||||||
<NavbarGroup>
|
<NavbarGroup>
|
||||||
<TransactionSelect
|
<ProjectTransactionsSelect
|
||||||
transactions={[
|
transactions={projectTranslations}
|
||||||
{ name: 'Invoice', path: 'invoices/new' },
|
|
||||||
{ name: 'Expenses', path: 'expenses/new' },
|
|
||||||
]}
|
|
||||||
onItemSelect={handleNewTransactionBtnClick}
|
onItemSelect={handleNewTransactionBtnClick}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
@@ -105,8 +111,6 @@ function ProjectDetailActionsBar({
|
|||||||
initialValue={timesheetsTableSize}
|
initialValue={timesheetsTableSize}
|
||||||
onChange={handleTableRowSizeChange}
|
onChange={handleTableRowSizeChange}
|
||||||
/>
|
/>
|
||||||
<NavbarDivider />
|
|
||||||
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
|
|
||||||
</NavbarGroup>
|
</NavbarGroup>
|
||||||
<NavbarGroup align={Alignment.RIGHT}>
|
<NavbarGroup align={Alignment.RIGHT}>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Tabs, Tab } from '@blueprintjs/core';
|
import { Tabs, Tab } from '@blueprintjs/core';
|
||||||
|
import ProjectTimeSheets from './ProjectTimeSheets';
|
||||||
import ProjectTimesheet from './ProjectTimesheet';
|
import ProjectPurchasesTable from './ProjectPurchasesTable';
|
||||||
|
import ProjectSalesTable from './ProjectSalesTable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project detail tabs.
|
* Project detail tabs.
|
||||||
@@ -16,19 +17,24 @@ export default function ProjectDetailTabs() {
|
|||||||
animate={true}
|
animate={true}
|
||||||
large={true}
|
large={true}
|
||||||
renderActiveTabPanelOnly={true}
|
renderActiveTabPanelOnly={true}
|
||||||
defaultSelectedTabId={'timesheet'}
|
defaultSelectedTabId={'purchases'}
|
||||||
>
|
>
|
||||||
<Tab id="overview" title={intl.get('project_details.label.overview')} />
|
<Tab id="overview" title={intl.get('project_details.label.overview')} />
|
||||||
<Tab
|
<Tab
|
||||||
id="timesheet"
|
id="timesheet"
|
||||||
title={intl.get('project_details.label.timesheet')}
|
title={intl.get('project_details.label.timesheet')}
|
||||||
panel={<ProjectTimesheet />}
|
panel={<ProjectTimeSheets />}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
id="purchases"
|
id="purchases"
|
||||||
title={intl.get('project_details.label.purchases')}
|
title={intl.get('project_details.label.purchases')}
|
||||||
|
panel={<ProjectPurchasesTable />}
|
||||||
|
/>
|
||||||
|
<Tab
|
||||||
|
id="sales"
|
||||||
|
title={intl.get('project_details.label.sales')}
|
||||||
|
panel={<ProjectSalesTable />}
|
||||||
/>
|
/>
|
||||||
<Tab id="sales" title={intl.get('project_details.label.sales')} />
|
|
||||||
<Tab id="journals" title={intl.get('project_details.label.journals')} />
|
<Tab id="journals" title={intl.get('project_details.label.journals')} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</ProjectTabsContent>
|
</ProjectTabsContent>
|
||||||
@@ -42,6 +48,10 @@ const ProjectTabsContent = styled.div`
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #d2dce2;
|
border-bottom: 1px solid #d2dce2;
|
||||||
|
|
||||||
|
> *:not(:last-child) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
&.bp3-large > .bp3-tab {
|
&.bp3-large > .bp3-tab {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -59,9 +69,11 @@ const ProjectTabsContent = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bp3-tab-panel {
|
.bp3-tab-panel {
|
||||||
margin-top: 20px;
|
/* margin: 20px 32px; */
|
||||||
|
/* margin: 20px; */
|
||||||
|
/* margin-top: 20px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
padding: 0 25px;
|
padding: 0 25px; */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
// @ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { DataTable } from 'components';
|
||||||
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
|
import { TABLES } from 'common/tables';
|
||||||
|
import { useMemorizedColumnsWidths } from 'hooks';
|
||||||
|
import { ActionMenu } from './components';
|
||||||
|
import { useProjectPurchasesColumns } from './hooks';
|
||||||
|
import withSettings from '../../../../Settings/withSettings';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project Purchases DataTable.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectPurchasesTableRoot({
|
||||||
|
// #withSettings
|
||||||
|
purchasesTableSize,
|
||||||
|
}) {
|
||||||
|
// Retrieve purchases table columns.
|
||||||
|
const columns = useProjectPurchasesColumns();
|
||||||
|
|
||||||
|
// Handle delete purchase.
|
||||||
|
const handleDeletePurchase = () => {};
|
||||||
|
|
||||||
|
// Local storage memorizing columns widths.
|
||||||
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
|
useMemorizedColumnsWidths(TABLES.PURCHASES);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={[]}
|
||||||
|
manualSortBy={true}
|
||||||
|
selectionColumn={true}
|
||||||
|
noInitialFetch={true}
|
||||||
|
sticky={true}
|
||||||
|
ContextMenu={ActionMenu}
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
|
initialColumnsWidths={initialColumnsWidths}
|
||||||
|
onColumnResizing={handleColumnResizing}
|
||||||
|
size={purchasesTableSize}
|
||||||
|
payload={{
|
||||||
|
onDelete: handleDeletePurchase,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const ProjectPurchasesTable = compose(
|
||||||
|
withSettings(({ purchasesSettings }) => ({
|
||||||
|
purchasesTableSize: purchasesSettings?.tableSize,
|
||||||
|
})),
|
||||||
|
)(ProjectPurchasesTableRoot);
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { Icon } from 'components';
|
||||||
|
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table actions cell.
|
||||||
|
*/
|
||||||
|
export function ActionMenu({ payload: { onDelete }, row: { original } }) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('purchases.action.delete')}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDelete, original)}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { FormatDateCell } from 'components';
|
||||||
|
|
||||||
|
export function useProjectPurchasesColumns() {
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'date',
|
||||||
|
Header: intl.get('purchases.column.date'),
|
||||||
|
accessor: 'date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
width: 120,
|
||||||
|
className: 'date',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
Header: intl.get('purchases.column.type'),
|
||||||
|
accessor: 'type',
|
||||||
|
width: 120,
|
||||||
|
className: 'type',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transaction_no',
|
||||||
|
Header: intl.get('purchases.column.transaction_no'),
|
||||||
|
accessor: 'transaction_no',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'due_date',
|
||||||
|
Header: intl.get('purchases.column.due_date'),
|
||||||
|
accessor: 'due_date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
width: 120,
|
||||||
|
className: 'due_date',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'balance',
|
||||||
|
Header: intl.get('purchases.column.balance'),
|
||||||
|
accessor: 'balance',
|
||||||
|
width: 120,
|
||||||
|
clickable: true,
|
||||||
|
align: 'right',
|
||||||
|
className: clsx(CLASSES.FONT_BOLD),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'total',
|
||||||
|
Header: intl.get('purchases.column.total'),
|
||||||
|
accessor: 'total',
|
||||||
|
align: 'right',
|
||||||
|
width: 120,
|
||||||
|
className: clsx(CLASSES.FONT_BOLD),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'status',
|
||||||
|
Header: intl.get('purchases.column.status'),
|
||||||
|
accessor: 'status',
|
||||||
|
width: 120,
|
||||||
|
className: 'status',
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ProjectPurchasesTable } from './ProjectPurchasesTable';
|
||||||
|
import { DashboardContentTable } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectPurchasesTableRoot() {
|
||||||
|
return (
|
||||||
|
<DashboardContentTable>
|
||||||
|
<ProjectPurchasesTable />
|
||||||
|
</DashboardContentTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { DataTable } from 'components';
|
||||||
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
|
import { TABLES } from 'common/tables';
|
||||||
|
import { useMemorizedColumnsWidths } from 'hooks';
|
||||||
|
import { ActionMenu } from './components';
|
||||||
|
import { useProjectSalesColumns } from './hooks';
|
||||||
|
import withSettings from '../../../../Settings/withSettings';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Porject sales datatable.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectSalesTableRoot({
|
||||||
|
// #withSettings
|
||||||
|
salesTableSize,
|
||||||
|
}) {
|
||||||
|
// Retrieve project sales table columns.
|
||||||
|
const columns = useProjectSalesColumns();
|
||||||
|
|
||||||
|
// Handle delete sale.
|
||||||
|
const handleDeleteSale = () => {};
|
||||||
|
|
||||||
|
// Local storage memorizing columns widths.
|
||||||
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
|
useMemorizedColumnsWidths(TABLES.SALES);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataTable
|
||||||
|
columns={columns}
|
||||||
|
data={[]}
|
||||||
|
manualSortBy={true}
|
||||||
|
selectionColumn={true}
|
||||||
|
noInitialFetch={true}
|
||||||
|
sticky={true}
|
||||||
|
ContextMenu={ActionMenu}
|
||||||
|
TableLoadingRenderer={TableSkeletonRows}
|
||||||
|
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||||
|
initialColumnsWidths={initialColumnsWidths}
|
||||||
|
onColumnResizing={handleColumnResizing}
|
||||||
|
size={salesTableSize}
|
||||||
|
payload={{
|
||||||
|
onDelete: handleDeleteSale,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export const ProjectSalesTable = compose(
|
||||||
|
withSettings(({ salesSettings }) => ({
|
||||||
|
salesTableSize: salesSettings?.tableSize,
|
||||||
|
})),
|
||||||
|
)(ProjectSalesTableRoot);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
import { FormatDateCell, Icon, FormattedMessage as T } from 'components';
|
||||||
|
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
||||||
|
import { safeCallback } from 'utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Table actions cell.
|
||||||
|
*/
|
||||||
|
export function ActionMenu({ payload: { onDelete }, row: { original } }) {
|
||||||
|
return (
|
||||||
|
<Menu>
|
||||||
|
<MenuItem
|
||||||
|
text={intl.get('sales.action.delete')}
|
||||||
|
intent={Intent.DANGER}
|
||||||
|
onClick={safeCallback(onDelete, original)}
|
||||||
|
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import clsx from 'classnames';
|
||||||
|
import { CLASSES } from 'common/classes';
|
||||||
|
import { FormatDateCell } from 'components';
|
||||||
|
|
||||||
|
export function useProjectSalesColumns() {
|
||||||
|
return useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'date',
|
||||||
|
Header: intl.get('sales.column.date'),
|
||||||
|
accessor: 'date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
width: 120,
|
||||||
|
className: 'date',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'type',
|
||||||
|
Header: intl.get('sales.column.type'),
|
||||||
|
accessor: 'type',
|
||||||
|
width: 120,
|
||||||
|
className: 'type',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'transaction_no',
|
||||||
|
Header: intl.get('sales.column.transaction_no'),
|
||||||
|
accessor: 'transaction_no',
|
||||||
|
width: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'due_date',
|
||||||
|
Header: intl.get('sales.column.due_date'),
|
||||||
|
accessor: 'due_date',
|
||||||
|
Cell: FormatDateCell,
|
||||||
|
width: 120,
|
||||||
|
className: 'due_date',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'balance',
|
||||||
|
Header: intl.get('sales.column.balance'),
|
||||||
|
accessor: 'balance',
|
||||||
|
width: 120,
|
||||||
|
clickable: true,
|
||||||
|
align: 'right',
|
||||||
|
className: clsx(CLASSES.FONT_BOLD),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'total',
|
||||||
|
Header: intl.get('sales.column.total'),
|
||||||
|
accessor: 'total',
|
||||||
|
align: 'right',
|
||||||
|
width: 120,
|
||||||
|
className: clsx(CLASSES.FONT_BOLD),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'status',
|
||||||
|
Header: intl.get('sales.column.status'),
|
||||||
|
accessor: 'status',
|
||||||
|
width: 120,
|
||||||
|
className: 'status',
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { ProjectSalesTable } from './ProjectSalesTable';
|
||||||
|
import { DashboardContentTable } from 'components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project Sales Table.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectSalesTableRoot() {
|
||||||
|
return (
|
||||||
|
<ProjectSalesContentTable>
|
||||||
|
<ProjectSalesTable />
|
||||||
|
</ProjectSalesContentTable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectSalesContentTable = styled(DashboardContentTable)``;
|
||||||
@@ -13,10 +13,10 @@ import {
|
|||||||
import { calculateStatus } from 'utils';
|
import { calculateStatus } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timesheets header
|
* Project Timesheets header
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export default function TimesheetsHeader() {
|
export function ProjectTimesheetsHeader() {
|
||||||
return (
|
return (
|
||||||
<DetailFinancialSection>
|
<DetailFinancialSection>
|
||||||
<DetailFinancialCard label={'Project estimate'} value={'3.14'} />
|
<DetailFinancialCard label={'Project estimate'} value={'3.14'} />
|
||||||
@@ -4,42 +4,25 @@ import styled from 'styled-components';
|
|||||||
import { DataTable } from 'components';
|
import { DataTable } from 'components';
|
||||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
import { useTimesheetColumns, ActionsMenu } from './components';
|
import { ActionsMenu } from './components';
|
||||||
|
import { useProjectTimesheetColumns } from './hooks';
|
||||||
import { TABLES } from 'common/tables';
|
import { TABLES } from 'common/tables';
|
||||||
import { useMemorizedColumnsWidths } from 'hooks';
|
import { useMemorizedColumnsWidths } from 'hooks';
|
||||||
import withSettings from '../../../../Settings/withSettings';
|
import withSettings from '../../../../Settings/withSettings';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const Timesheet = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
date: '2022-06-08T22:00:00.000Z',
|
|
||||||
name: 'Lighting',
|
|
||||||
display_name: 'Kyrie Rearden',
|
|
||||||
description: 'Laid paving stones',
|
|
||||||
duration: '12:00',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
date: '2022-06-08T22:00:00.000Z',
|
|
||||||
name: 'Interior Decoration',
|
|
||||||
display_name: 'Project Sherwood',
|
|
||||||
description: 'Laid paving stones',
|
|
||||||
duration: '11:00',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Timesheet DataTable.
|
* Timesheet DataTable.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TimesheetsTable({
|
function ProjectTimesheetsTableRoot({
|
||||||
// #withSettings
|
// #withSettings
|
||||||
timesheetsTableSize,
|
timesheetsTableSize,
|
||||||
}) {
|
}) {
|
||||||
// Retrieve timesheet table columns.
|
// Retrieve project timesheet table columns.
|
||||||
const columns = useTimesheetColumns();
|
const columns = useProjectTimesheetColumns();
|
||||||
|
|
||||||
// Handle delete timesheet.
|
// Handle delete timesheet.
|
||||||
const handleDeleteTimesheet = () => {};
|
const handleDeleteTimesheet = () => {};
|
||||||
@@ -49,12 +32,9 @@ function TimesheetsTable({
|
|||||||
useMemorizedColumnsWidths(TABLES.TIMESHEETS);
|
useMemorizedColumnsWidths(TABLES.TIMESHEETS);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TimesheetDataTable
|
<ProjectTimesheetDataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={Timesheet}
|
data={[]}
|
||||||
// loading={}
|
|
||||||
// headerLoading={}
|
|
||||||
// progressBarLoading={}
|
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
@@ -71,13 +51,13 @@ function TimesheetsTable({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default compose(
|
export const ProjectTimesheetsTable = compose(
|
||||||
withSettings(({ timesheetsSettings }) => ({
|
withSettings(({ timesheetsSettings }) => ({
|
||||||
timesheetsTableSize: timesheetsSettings?.tableSize,
|
timesheetsTableSize: timesheetsSettings?.tableSize,
|
||||||
})),
|
})),
|
||||||
)(TimesheetsTable);
|
)(ProjectTimesheetsTableRoot);
|
||||||
|
|
||||||
const TimesheetDataTable = styled(DataTable)`
|
const ProjectTimesheetDataTable = styled(DataTable)`
|
||||||
.table {
|
.table {
|
||||||
.thead .tr .th {
|
.thead .tr .th {
|
||||||
.resizer {
|
.resizer {
|
||||||
@@ -87,38 +67,38 @@ const TimesheetDataTable = styled(DataTable)`
|
|||||||
|
|
||||||
.tbody {
|
.tbody {
|
||||||
.tr .td {
|
.tr .td {
|
||||||
padding: 0.5rem 0.8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar.td {
|
.avatar.td {
|
||||||
.avatar {
|
.cell-inner {
|
||||||
display: inline-block;
|
.avatar {
|
||||||
background: #adbcc9;
|
display: inline-block;
|
||||||
border-radius: 50%;
|
background: #adbcc9;
|
||||||
text-align: center;
|
border-radius: 50%;
|
||||||
font-weight: 400;
|
text-align: center;
|
||||||
color: #fff;
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
&[data-size='medium'] {
|
&[data-size='medium'] {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
&[data-size='small'] {
|
&[data-size='small'] {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.table-size--small {
|
.table-size--small {
|
||||||
.tbody .tr {
|
.tbody .tr {
|
||||||
height: 45px;
|
height: 45px;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -1,15 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { FormatDate, Icon, FormattedMessage as T } from 'components';
|
import { FormatDate, Icon } from 'components';
|
||||||
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
||||||
import { safeCallback, firstLettersArgs } from 'utils';
|
import { safeCallback, firstLettersArgs } from 'utils';
|
||||||
import { chain } from 'lodash';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table actions cell.
|
* Table actions cell.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function ActionsMenu({
|
export function ActionsMenu({
|
||||||
payload: { onDelete, onViewDetails },
|
payload: { onDelete, onViewDetails },
|
||||||
row: { original },
|
row: { original },
|
||||||
@@ -78,43 +76,3 @@ const TimesheetDescription = styled.span`
|
|||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve timesheet list columns columns.
|
|
||||||
*/
|
|
||||||
export function useTimesheetColumns() {
|
|
||||||
return React.useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
id: 'avatar',
|
|
||||||
Header: '',
|
|
||||||
Cell: AvatarCell,
|
|
||||||
className: 'avatar',
|
|
||||||
width: 45,
|
|
||||||
disableResizing: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
clickable: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'name',
|
|
||||||
Header: 'Header',
|
|
||||||
accessor: TimesheetAccessor,
|
|
||||||
width: 100,
|
|
||||||
className: 'name',
|
|
||||||
clickable: true,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'duration',
|
|
||||||
Header: '',
|
|
||||||
accessor: 'duration',
|
|
||||||
width: 100,
|
|
||||||
className: 'duration',
|
|
||||||
align: 'right',
|
|
||||||
clickable: true,
|
|
||||||
textOverview: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import intl from 'react-intl-universal';
|
||||||
|
import { AvatarCell, TimesheetAccessor } from './components';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve project timesheet list columns.
|
||||||
|
*/
|
||||||
|
export function useProjectTimesheetColumns() {
|
||||||
|
return React.useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
id: 'avatar',
|
||||||
|
Header: '',
|
||||||
|
Cell: AvatarCell,
|
||||||
|
className: 'avatar',
|
||||||
|
width: 45,
|
||||||
|
disableResizing: true,
|
||||||
|
disableSortBy: true,
|
||||||
|
clickable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
Header: 'Header',
|
||||||
|
accessor: TimesheetAccessor,
|
||||||
|
width: 100,
|
||||||
|
className: 'name',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'duration',
|
||||||
|
Header: '',
|
||||||
|
accessor: 'duration',
|
||||||
|
width: 100,
|
||||||
|
className: 'duration',
|
||||||
|
align: 'right',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import { ProjectTimesheetsTable } from './ProjectTimesheetsTable';
|
||||||
|
import { ProjectTimesheetsHeader } from './ProjectTimesheetsHeader';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project Timesheets.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectTimeSheets() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<ProjectTimesheetsHeader />
|
||||||
|
<ProjectTimesheetTableCard>
|
||||||
|
<ProjectTimesheetsTable />
|
||||||
|
</ProjectTimesheetTableCard>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProjectTimesheetTableCard = styled.div`
|
||||||
|
margin: 22px 32px;
|
||||||
|
border: 1px solid #c8cad0;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #fff;
|
||||||
|
`;
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
import TimesheetsTable from './TimesheetsTable';
|
|
||||||
import TimesheetsHeader from './TimesheetsHeader';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Project Timesheet.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export default function ProjectTimesheet() {
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
<TimesheetsHeader />
|
|
||||||
<ProjectTimesheetTableCard>
|
|
||||||
<TimesheetsTable />
|
|
||||||
</ProjectTimesheetTableCard>
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const ProjectTimesheetTableCard = styled.div`
|
|
||||||
margin: 20px;
|
|
||||||
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
|
||||||
border-radius: 3px;
|
|
||||||
background: #fff;
|
|
||||||
`;
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
export const projectTranslations = [
|
||||||
|
{ name: intl.get('project_details.new_expenses'), path: 'expense' },
|
||||||
|
{
|
||||||
|
name: intl.get('project_details.new_estimated_expenses'),
|
||||||
|
path: 'estimated_expense',
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -29,17 +29,16 @@ export const FinancialProgressBar = ({ ...rest }) => {
|
|||||||
|
|
||||||
const FinancialSectionWrap = styled.div`
|
const FinancialSectionWrap = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
margin: 22px 32px;
|
||||||
margin: 20px 20px 20px;
|
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FinancialSectionCard = styled.div`
|
const FinancialSectionCard = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex-shrink: 0;
|
flex-shrink: 1;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 220px;
|
width: 230px;
|
||||||
height: 116px;
|
height: 116px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
||||||
@@ -47,7 +46,6 @@ const FinancialSectionCard = styled.div`
|
|||||||
|
|
||||||
const FinancialSectionCardContent = styled.div`
|
const FinancialSectionCardContent = styled.div`
|
||||||
margin: 16px;
|
margin: 16px;
|
||||||
/* flex-direction: column; */
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FinancialCardWrap = styled.div``;
|
const FinancialCardWrap = styled.div``;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { Icon, FormattedMessage as T } from 'components';
|
|||||||
* @param {*} param1
|
* @param {*} param1
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const transactionItemRenderer = (
|
const projectTransactionItemRenderer = (
|
||||||
transaction,
|
transaction,
|
||||||
{ handleClick, modifiers, query },
|
{ handleClick, modifiers, query },
|
||||||
) => {
|
) => {
|
||||||
@@ -29,8 +29,8 @@ const transactionItemRenderer = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const transactionSelectProps = {
|
const projectTransactionSelectProps = {
|
||||||
itemRenderer: transactionItemRenderer,
|
itemRenderer: projectTransactionItemRenderer,
|
||||||
filterable: false,
|
filterable: false,
|
||||||
popoverProps: {
|
popoverProps: {
|
||||||
minimal: true,
|
minimal: true,
|
||||||
@@ -43,13 +43,13 @@ const transactionSelectProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* Project transactions select
|
||||||
* @param
|
* @param
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function TransactionSelect({ transactions, ...rest }) {
|
export function ProjectTransactionsSelect({ transactions, ...rest }) {
|
||||||
return (
|
return (
|
||||||
<Select {...transactionSelectProps} items={transactions} {...rest}>
|
<Select {...projectTransactionSelectProps} items={transactions} {...rest}>
|
||||||
<Button
|
<Button
|
||||||
minimal={true}
|
minimal={true}
|
||||||
icon={<Icon icon={'plus'} />}
|
icon={<Icon icon={'plus'} />}
|
||||||
@@ -1,3 +1,2 @@
|
|||||||
export * from './ProjectSelect';
|
export * from './ProjectTransactionsSelect';
|
||||||
export * from './TransactionSelect';
|
|
||||||
export * from './FinancialSection';
|
export * from './FinancialSection';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
import React from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import ProjectDetailActionsBar from './ProjectDetailActionsBar';
|
import ProjectDetailActionsBar from './ProjectDetailActionsBar';
|
||||||
import ProjectDetailTabs from './ProjectDetailTabs';
|
import ProjectDetailTabs from './ProjectDetailTabs';
|
||||||
@@ -20,7 +20,7 @@ function ProjectTabs({
|
|||||||
state: { projectName, projectId },
|
state: { projectName, projectId },
|
||||||
} = useLocation();
|
} = useLocation();
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
changePageTitle(projectName);
|
changePageTitle(projectName);
|
||||||
}, [changePageTitle, projectName]);
|
}, [changePageTitle, projectName]);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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('project_expense.schema.label.expense_name'),
|
||||||
|
),
|
||||||
|
estimatedExpense: Yup.number().label(
|
||||||
|
intl.get('project_expense.schema.label.estimated_expense'),
|
||||||
|
),
|
||||||
|
expemseDate: Yup.date(),
|
||||||
|
expenseQuantity: Yup.number().label(
|
||||||
|
intl.get('project_expense.schema.label.quantity'),
|
||||||
|
),
|
||||||
|
expenseUnitPrice: Yup.number().label(
|
||||||
|
intl.get('project_expense.schema.label.unitPrice'),
|
||||||
|
),
|
||||||
|
expenseTotal: Yup.number(),
|
||||||
|
expenseCharge: Yup.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateProjectExpenseFormSchema = Schema;
|
||||||
@@ -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 { CreateProjectExpenseFormSchema } from './ProjectExpenseForm.schema';
|
||||||
|
import ProjectExpenseFormContent from './ProjectExpenseFormContent';
|
||||||
|
import { useProjectExpenseFormContext } from './ProjectExpenseFormProvider';
|
||||||
|
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: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project expense form.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectExpenseForm({
|
||||||
|
//#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 (
|
||||||
|
<Formik
|
||||||
|
validationSchema={CreateProjectExpenseFormSchema}
|
||||||
|
initialValues={initialValues}
|
||||||
|
onSubmit={handleFormSubmit}
|
||||||
|
component={ProjectExpenseFormContent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ProjectExpenseForm);
|
||||||
@@ -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 (
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('expenses.dialog.percentage')}
|
||||||
|
name={'percentage'}
|
||||||
|
>
|
||||||
|
<FInputGroup name="percentage" />
|
||||||
|
</FFormGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomPirceField() {
|
||||||
|
return (
|
||||||
|
<ControlGroup className={Classes.FILL}>
|
||||||
|
<FFormGroup
|
||||||
|
name={'expenseUnitPrice'}
|
||||||
|
label={intl.get('expenses.dialog.unit_price')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="expenseUnitPrice" />
|
||||||
|
</FFormGroup>
|
||||||
|
<FFormGroup
|
||||||
|
name={'expenseTotal'}
|
||||||
|
label={intl.get('expenses.dialog.total')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="expenseTotal" />
|
||||||
|
</FFormGroup>
|
||||||
|
</ControlGroup>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense form charge fields.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ExpenseFormChargeFields() {
|
||||||
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Choose>
|
||||||
|
<Choose.When condition={values.expenseCharge === 'markup'}>
|
||||||
|
<PercentageFormField />
|
||||||
|
</Choose.When>
|
||||||
|
<Choose.When condition={values.expenseCharge === 'custom_pirce'}>
|
||||||
|
<CustomPirceField />
|
||||||
|
</Choose.When>
|
||||||
|
</Choose>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import ProjectExpenseFormFields from './ProjectExpenseFormFields';
|
||||||
|
import ProjectExpneseFormFloatingActions from './ProjectExpneseFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expense form content.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectExpenseFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<ProjectExpenseFormFields />
|
||||||
|
<ProjectExpneseFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ProjectExpenseFormProvider } from './ProjectExpenseFormProvider';
|
||||||
|
import ProjectExpenseForm from './ProjectExpenseForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project expense form dialog content.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectExpenseFormDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
expense,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProjectExpenseFormProvider dialogName={dialogName} expenseId={expense}>
|
||||||
|
<ProjectExpenseForm />
|
||||||
|
</ProjectExpenseFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
//@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, FInputGroupComponent } from '../../components';
|
||||||
|
import ExpenseFormChargeFields from './ProjectExpenseFormChargeFields';
|
||||||
|
import { momentFormatter } from 'utils';
|
||||||
|
import { useProjectExpenseFormContext } from './ProjectExpenseFormProvider';
|
||||||
|
import { ChangeTypesSelect } from '../../components';
|
||||||
|
import { expenseChargeOption } from 'containers/Projects/containers/common/modalChargeOptions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project expense form fields.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function ProjectExpenseFormFields() {
|
||||||
|
return (
|
||||||
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
{/*------------ Expense Name -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('project_expense.dialog.expense_name')}
|
||||||
|
name={'expenseName'}
|
||||||
|
>
|
||||||
|
<FInputGroup name="expenseName" />
|
||||||
|
</FFormGroup>
|
||||||
|
{/*------------ Track to Expense -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'estimatedExpense'}
|
||||||
|
label={intl.get('project_expense.dialog.track_expense')}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<ExpenseSelect
|
||||||
|
name={'estimatedExpense'}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
expenses={[{ id: 1, name: 'Expense 1' }]}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Extimated Date -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('project_expense.dialog.expense_date')}
|
||||||
|
name={'expemseDate'}
|
||||||
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
|
>
|
||||||
|
<FDateInput
|
||||||
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
|
name="expemseDate"
|
||||||
|
formatDate={(date) => date.toLocaleString()}
|
||||||
|
popoverProps={{
|
||||||
|
position: Position.BOTTOM,
|
||||||
|
minimal: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
{/*------------ Quantity -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
label={intl.get('project_expense.dialog.quantity')}
|
||||||
|
name={'expenseQuantity'}
|
||||||
|
>
|
||||||
|
<FInputGroupComponent name="expenseQuantity" />
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
<MetaLineLabel>
|
||||||
|
<T id={'project_expense.dialog.cost_to_you'} />
|
||||||
|
</MetaLineLabel>
|
||||||
|
{/*------------ Unit Price -----------*/}
|
||||||
|
<ControlGroup className={Classes.FILL}>
|
||||||
|
<FFormGroup
|
||||||
|
name={'unitPrice'}
|
||||||
|
label={intl.get('project_expense.dialog.unit_price')}
|
||||||
|
>
|
||||||
|
<FInputGroupComponent name="expenseUnitPrice" />
|
||||||
|
</FFormGroup>
|
||||||
|
<FFormGroup
|
||||||
|
name={'expenseTotal'}
|
||||||
|
label={intl.get('project_expense.dialog.expense_total')}
|
||||||
|
>
|
||||||
|
<FInputGroup name="expenseTotal" />
|
||||||
|
</FFormGroup>
|
||||||
|
</ControlGroup>
|
||||||
|
|
||||||
|
<MetaLineLabel>
|
||||||
|
<T id={'project_expense.dialog.what_you_ll_charge'} />
|
||||||
|
</MetaLineLabel>
|
||||||
|
{/*------------ Charge -----------*/}
|
||||||
|
<FFormGroup
|
||||||
|
name={'expenseCharge'}
|
||||||
|
label={<T id={'project_expense.dialog.charge'} />}
|
||||||
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
|
>
|
||||||
|
<ChangeTypesSelect
|
||||||
|
name="expenseCharge"
|
||||||
|
items={expenseChargeOption}
|
||||||
|
popoverProps={{ minimal: true }}
|
||||||
|
filterable={false}
|
||||||
|
/>
|
||||||
|
</FFormGroup>
|
||||||
|
|
||||||
|
{/*------------ Charge Fields -----------*/}
|
||||||
|
<ExpenseFormChargeFields />
|
||||||
|
|
||||||
|
{/*------------ Total -----------*/}
|
||||||
|
<ExpenseTotalBase>
|
||||||
|
<ExpenseTotalLabel>
|
||||||
|
<T id={'project_expense.dialog.total'} />
|
||||||
|
</ExpenseTotalLabel>
|
||||||
|
<ExpenseTotal>0.00</ExpenseTotal>
|
||||||
|
</ExpenseTotalBase>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
|
const ProjectExpenseFormContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project expense form provider.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectExpenseFormProvider({
|
||||||
|
//#OwnProps
|
||||||
|
dialogName,
|
||||||
|
expenseId,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// state provider.
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<ProjectExpenseFormContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useProjectExpenseFormContext = () => React.useContext(ProjectExpenseFormContext);
|
||||||
|
export { ProjectExpenseFormProvider, useProjectExpenseFormContext };
|
||||||
@@ -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 { useProjectExpenseFormContext } from './ProjectExpenseFormProvider';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function ProjectExpneseFormFloatingActions({
|
||||||
|
// #withDialogActions
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
// Formik context.
|
||||||
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// expense form dialog context.
|
||||||
|
const { dialogName } = useProjectExpenseFormContext();
|
||||||
|
|
||||||
|
// 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: '75px' }}>
|
||||||
|
<T id={'cancel'} />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
loading={isSubmitting}
|
||||||
|
style={{ minWidth: '75px' }}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{<T id={'save'} />}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ProjectExpneseFormFloatingActions);
|
||||||
@@ -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 ProjectExpenseFormeDialogContent = React.lazy(
|
||||||
|
() => import('./ProjectExpenseFormDialogContent'),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project expense form dialog.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectExpenseFormDialog({
|
||||||
|
dialogName,
|
||||||
|
payload: { projectId = null },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProjectExpenseFormDialogRoot
|
||||||
|
name={dialogName}
|
||||||
|
title={<T id={'project_expense.dialog.label'} />}
|
||||||
|
isOpen={isOpen}
|
||||||
|
autoFocus={true}
|
||||||
|
canEscapeKeyClose={true}
|
||||||
|
style={{ width: '400px' }}
|
||||||
|
>
|
||||||
|
<DialogSuspense>
|
||||||
|
<ProjectExpenseFormeDialogContent
|
||||||
|
dialogName={dialogName}
|
||||||
|
expense={projectId}
|
||||||
|
/>
|
||||||
|
</DialogSuspense>
|
||||||
|
</ProjectExpenseFormDialogRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogRedux())(ProjectExpenseFormDialog);
|
||||||
|
|
||||||
|
const ProjectExpenseFormDialogRoot = 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;
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,19 +1,18 @@
|
|||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
|
||||||
|
|
||||||
const Schema = Yup.object().shape({
|
const Schema = Yup.object().shape({
|
||||||
contact: Yup.string().label(intl.get('project.schema.label.contact')),
|
contact_id: Yup.string().label(intl.get('project.schema.label.contact')),
|
||||||
projectName: Yup.string()
|
name: Yup.string()
|
||||||
.label(intl.get('project.schema.label.project_name'))
|
.label(intl.get('project.schema.label.project_name'))
|
||||||
.required(),
|
.required(),
|
||||||
projectDeadline: Yup.date()
|
deadline: Yup.date()
|
||||||
.label(intl.get('project.schema.label.deadline'))
|
.label(intl.get('project.schema.label.deadline'))
|
||||||
.required(),
|
.required(),
|
||||||
projectState: Yup.boolean().label(
|
published: Yup.boolean().label(
|
||||||
intl.get('project.schema.label.project_state'),
|
intl.get('project.schema.label.project_state'),
|
||||||
),
|
),
|
||||||
projectCost: Yup.number().label(
|
cost_estimate: Yup.number().label(
|
||||||
intl.get('project.schema.label.project_cost'),
|
intl.get('project.schema.label.project_cost'),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ import React from 'react';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
|
import { Intent } from '@blueprintjs/core';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import ProjectFormContent from './ProjectFormContent';
|
import ProjectFormContent from './ProjectFormContent';
|
||||||
import { CreateProjectFormSchema } from './ProjectForm.schema';
|
import { CreateProjectFormSchema } from './ProjectForm.schema';
|
||||||
import { useProjectFormContext } from './ProjectFormProvider';
|
import { useProjectFormContext } from './ProjectFormProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose, transformToForm } from 'utils';
|
||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
contact: '',
|
contact_id: '',
|
||||||
projectName: '',
|
name: '',
|
||||||
projectDeadline: moment(new Date()).format('YYYY-MM-DD'),
|
deadline: moment(new Date()).format('YYYY-MM-DD'),
|
||||||
projectState: false,
|
published: false,
|
||||||
projectCost: '',
|
cost_estimate: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,20 +29,37 @@ function ProjectForm({
|
|||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// project form dialog context.
|
// project form dialog context.
|
||||||
const { dialogName } = useProjectFormContext();
|
const {
|
||||||
|
dialogName,
|
||||||
|
project,
|
||||||
|
isNewMode,
|
||||||
|
projectId,
|
||||||
|
createProjectMutate,
|
||||||
|
editProjectMutate,
|
||||||
|
} = useProjectFormContext();
|
||||||
|
|
||||||
// Initial form values
|
// Initial form values
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
...defaultInitialValues,
|
...defaultInitialValues,
|
||||||
|
...transformToForm(project, defaultInitialValues),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles the form submit.
|
// Handles the form submit.
|
||||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||||
const form = {};
|
setSubmitting(true);
|
||||||
|
const form = { ...values };
|
||||||
|
|
||||||
// Handle request response success.
|
// Handle request response success.
|
||||||
const onSuccess = (response) => {
|
const onSuccess = (response) => {
|
||||||
AppToaster.show({});
|
AppToaster.show({
|
||||||
|
message: intl.get(
|
||||||
|
isNewMode
|
||||||
|
? 'projects.dialog.success_message'
|
||||||
|
: 'projects.dialog.edit_success_message',
|
||||||
|
),
|
||||||
|
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,6 +71,12 @@ function ProjectForm({
|
|||||||
}) => {
|
}) => {
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isNewMode) {
|
||||||
|
createProjectMutate(form).then(onSuccess).catch(onError);
|
||||||
|
} else {
|
||||||
|
editProjectMutate([projectId, form]).then(onSuccess).catch(onError);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function ProjectFormFields() {
|
|||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
{/*------------ Contact -----------*/}
|
{/*------------ Contact -----------*/}
|
||||||
<FastField name={'contact'}>
|
<FastField name={'contact_id'}>
|
||||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={intl.get('projects.dialog.contact')}
|
label={intl.get('projects.dialog.contact')}
|
||||||
@@ -53,7 +53,7 @@ function ProjectFormFields() {
|
|||||||
selectedContactId={value}
|
selectedContactId={value}
|
||||||
defaultSelectText={'Select Contact Account'}
|
defaultSelectText={'Select Contact Account'}
|
||||||
onContactSelected={(customer) => {
|
onContactSelected={(customer) => {
|
||||||
form.setFieldValue('contact', customer.id);
|
form.setFieldValue('contact_id', customer.id);
|
||||||
}}
|
}}
|
||||||
allowCreate={true}
|
allowCreate={true}
|
||||||
popoverFill={true}
|
popoverFill={true}
|
||||||
@@ -64,19 +64,20 @@ function ProjectFormFields() {
|
|||||||
{/*------------ Project Name -----------*/}
|
{/*------------ Project Name -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={intl.get('projects.dialog.project_name')}
|
label={intl.get('projects.dialog.project_name')}
|
||||||
name={'projectName'}
|
name={'name'}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
>
|
>
|
||||||
<FInputGroup name="projectName" />
|
<FInputGroup name="name" />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
{/*------------ DeadLine -----------*/}
|
{/*------------ DeadLine -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={intl.get('projects.dialog.deadline')}
|
label={intl.get('projects.dialog.deadline')}
|
||||||
name={'projectDeadline'}
|
name={'deadline'}
|
||||||
className={classNames(CLASSES.FILL, 'form-group--date')}
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
>
|
>
|
||||||
<FDateInput
|
<FDateInput
|
||||||
{...momentFormatter('YYYY/MM/DD')}
|
{...momentFormatter('YYYY/MM/DD')}
|
||||||
name="projectDeadline"
|
name="deadline"
|
||||||
formatDate={(date) => date.toLocaleString()}
|
formatDate={(date) => date.toLocaleString()}
|
||||||
popoverProps={{
|
popoverProps={{
|
||||||
position: Position.BOTTOM,
|
position: Position.BOTTOM,
|
||||||
@@ -86,22 +87,23 @@ function ProjectFormFields() {
|
|||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
|
|
||||||
{/*------------ CheckBox -----------*/}
|
{/*------------ CheckBox -----------*/}
|
||||||
<FFormGroup name={'projectState'}>
|
<FFormGroup name={'published'}>
|
||||||
<FCheckbox
|
<FCheckbox
|
||||||
name="projectState"
|
name="published"
|
||||||
label={intl.get('projects.dialog.calculator_expenses')}
|
label={intl.get('projects.dialog.calculator_expenses')}
|
||||||
/>
|
/>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
{/*------------ Cost Estimate -----------*/}
|
{/*------------ Cost Estimate -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'projectCost'}
|
name={'cost_estimate'}
|
||||||
label={intl.get('projects.dialog.cost_estimate')}
|
label={intl.get('projects.dialog.cost_estimate')}
|
||||||
|
labelInfo={<FieldRequiredHint />}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<InputPrependText text={'USD'} />
|
<InputPrependText text={'USD'} />
|
||||||
<FMoneyInputGroup
|
<FMoneyInputGroup
|
||||||
disabled={values.projectState}
|
disabled={values.published}
|
||||||
name={'project_cost'}
|
name={'cost_estimate'}
|
||||||
allowDecimals={true}
|
allowDecimals={true}
|
||||||
allowNegativeValue={true}
|
allowNegativeValue={true}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ function ProjectFormFloatingActions({
|
|||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// project form dialog context.
|
|
||||||
const { dialogName } = useProjectFormContext();
|
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
|
// project form dialog context.
|
||||||
|
const { dialogName } = useProjectFormContext();
|
||||||
|
|
||||||
// Handle close button click.
|
// Handle close button click.
|
||||||
const handleCancelBtnClick = () => {
|
const handleCancelBtnClick = () => {
|
||||||
closeDialog(dialogName);
|
closeDialog(dialogName);
|
||||||
@@ -29,7 +29,7 @@ function ProjectFormFloatingActions({
|
|||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_FOOTER}>
|
<div className={Classes.DIALOG_FOOTER}>
|
||||||
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
|
||||||
<Button onClick={handleCancelBtnClick} style={{ minWidth: '75px' }}>
|
<Button onClick={handleCancelBtnClick} style={{ minWidth: '85px' }}>
|
||||||
<T id={'cancel'} />
|
<T id={'cancel'} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useCustomers } from 'hooks/query';
|
import { useCustomers } from 'hooks/query';
|
||||||
|
import { useCreateProject, useEditProject, useProject } from '../../hooks';
|
||||||
import { DialogContent } from 'components';
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
const ProjectFormContext = React.createContext();
|
const ProjectFormContext = React.createContext();
|
||||||
@@ -15,20 +16,36 @@ function ProjectFormProvider({
|
|||||||
projectId,
|
projectId,
|
||||||
...props
|
...props
|
||||||
}) {
|
}) {
|
||||||
|
// Create and edit project mutations.
|
||||||
|
const { mutateAsync: createProjectMutate } = useCreateProject();
|
||||||
|
const { mutateAsync: editProjectMutate } = useEditProject();
|
||||||
|
|
||||||
|
// Handle fetch project detail.
|
||||||
|
const { data: project, isLoading: isProjectLoading } = useProject(projectId, {
|
||||||
|
enabled: !!projectId,
|
||||||
|
});
|
||||||
|
|
||||||
// Handle fetch customers data table or list
|
// Handle fetch customers data table or list
|
||||||
const {
|
const {
|
||||||
data: { customers },
|
data: { customers },
|
||||||
isLoading: isCustomersLoading,
|
isLoading: isCustomersLoading,
|
||||||
} = useCustomers({ page_size: 10000 });
|
} = useCustomers({ page_size: 10000 });
|
||||||
|
|
||||||
|
const isNewMode = !projectId;
|
||||||
|
|
||||||
// State provider.
|
// State provider.
|
||||||
const provider = {
|
const provider = {
|
||||||
customers,
|
customers,
|
||||||
dialogName,
|
dialogName,
|
||||||
|
project,
|
||||||
|
projectId,
|
||||||
|
isNewMode,
|
||||||
|
createProjectMutate,
|
||||||
|
editProjectMutate,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogContent isLoading={isCustomersLoading}>
|
<DialogContent isLoading={isCustomersLoading || isProjectLoading}>
|
||||||
<ProjectFormContext.Provider value={provider} {...props} />
|
<ProjectFormContext.Provider value={provider} {...props} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -14,13 +14,19 @@ const ProjectDialogContent = React.lazy(
|
|||||||
*/
|
*/
|
||||||
function ProjectFormDialog({
|
function ProjectFormDialog({
|
||||||
dialogName,
|
dialogName,
|
||||||
payload: { projectId = null },
|
payload: { projectId = null, action },
|
||||||
isOpen,
|
isOpen,
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<ProjectFormDialogRoot
|
<ProjectFormDialogRoot
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
title={<T id={'projects.label.new_project'} />}
|
title={
|
||||||
|
action === 'edit' ? (
|
||||||
|
<T id="projects.dialog.edit_project" />
|
||||||
|
) : (
|
||||||
|
<T id={'projects.dialog.new_project'} />
|
||||||
|
)
|
||||||
|
}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
canEscapeKeyClose={true}
|
canEscapeKeyClose={true}
|
||||||
@@ -39,6 +45,7 @@ const ProjectFormDialogRoot = styled(Dialog)`
|
|||||||
.bp3-dialog-body {
|
.bp3-dialog-body {
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
|
margin-top: 15px;
|
||||||
|
|
||||||
label.bp3-label {
|
label.bp3-label {
|
||||||
margin-bottom: 3px;
|
margin-bottom: 3px;
|
||||||
|
|||||||
@@ -7,8 +7,10 @@ const Schema = Yup.object().shape({
|
|||||||
.label(intl.get('task.schema.label.task_name'))
|
.label(intl.get('task.schema.label.task_name'))
|
||||||
.required(),
|
.required(),
|
||||||
taskHouse: Yup.string().label(intl.get('task.schema.label.task_house')),
|
taskHouse: Yup.string().label(intl.get('task.schema.label.task_house')),
|
||||||
taskCharge: Yup.string().label(intl.get('task.schema.label.charge')).required(),
|
taskCharge: Yup.string()
|
||||||
|
.label(intl.get('task.schema.label.charge'))
|
||||||
|
.required(),
|
||||||
taskamount: Yup.number().label(intl.get('task.schema.label.amount')),
|
taskamount: Yup.number().label(intl.get('task.schema.label.amount')),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateTaskFormSchema = Schema;
|
export const CreateProjectTaskFormSchema = Schema;
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { CreateTaskFormSchema } from './TaskForm.schema';
|
import { CreateProjectTaskFormSchema } from './ProjectTaskForm.schema';
|
||||||
import { useTaskFormContext } from './TaskFormProvider';
|
import { useProjectTaskFormContext } from './ProjectTaskFormProvider';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
import TaskFormContent from './TaskFormContent';
|
import ProjectTaskFormContent from './ProjectTaskFormContent';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -12,20 +12,20 @@ import { compose } from 'utils';
|
|||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
taskName: '',
|
taskName: '',
|
||||||
taskHouse: '00:00',
|
taskHouse: '00:00',
|
||||||
taskCharge: 'Hourly rate',
|
taskCharge: 'hourly_rate',
|
||||||
taskamount: '100000000',
|
taskamount: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task form.
|
* Project task form.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TaskForm({
|
function ProjectTaskForm({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// task form dialog context.
|
// task form dialog context.
|
||||||
const { dialogName } = useTaskFormContext();
|
const { dialogName } = useProjectTaskFormContext();
|
||||||
|
|
||||||
// Initial form values
|
// Initial form values
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
@@ -51,12 +51,12 @@ function TaskForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
validationSchema={CreateTaskFormSchema}
|
validationSchema={CreateProjectTaskFormSchema}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
component={TaskFormContent}
|
component={ProjectTaskFormContent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(TaskForm);
|
export default compose(withDialogActions)(ProjectTaskForm);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import ProjectTaskFormFields from './ProjectTaskFormFields';
|
||||||
|
import ProjectTaskFormFloatingActions from './ProjectTaskFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task form content.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function TaskFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<ProjectTaskFormFields />
|
||||||
|
<ProjectTaskFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ProjectTaskFormProvider } from './ProjectTaskFormProvider';
|
||||||
|
import ProjectTaskForm from './ProjectTaskForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project task form dialog content.
|
||||||
|
*/
|
||||||
|
export default function ProjectTaskFormDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
task,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProjectTaskFormProvider taskId={task} dialogName={dialogName}>
|
||||||
|
<ProjectTaskForm />
|
||||||
|
</ProjectTaskFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//@ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
@@ -9,29 +10,28 @@ import {
|
|||||||
Row,
|
Row,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { modalChargeOptions } from '../../../../common/modalChargeOptions';
|
import { taskChargeOptions } from 'containers/Projects/containers/common/modalChargeOptions';
|
||||||
|
import { ChangeTypesSelect } from '../../components';
|
||||||
import { TaskModalChargeSelect } from './components';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task form fields.
|
* Project task form fields.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TaskFormFields() {
|
function ProjectTaskFormFields() {
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { values } = useFormikContext();
|
const { values } = useFormikContext();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
{/*------------ Task Name -----------*/}
|
{/*------------ Task Name -----------*/}
|
||||||
<FFormGroup label={<T id={'task.dialog.task_name'} />} name={'taskName'}>
|
<FFormGroup label={<T id={'project_task.dialog.task_name'} />} name={'taskName'}>
|
||||||
<FInputGroup name="taskName" />
|
<FInputGroup name="taskName" />
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
{/*------------ Estimated Hours -----------*/}
|
{/*------------ Estimated Hours -----------*/}
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs={4}>
|
<Col xs={4}>
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={<T id={'task.dialog.estimated_hours'} />}
|
label={<T id={'project_task.dialog.estimated_hours'} />}
|
||||||
name={'taskHouse'}
|
name={'taskHouse'}
|
||||||
>
|
>
|
||||||
<FInputGroup name="taskHouse" />
|
<FInputGroup name="taskHouse" />
|
||||||
@@ -42,16 +42,19 @@ function TaskFormFields() {
|
|||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'taskCharge'}
|
name={'taskCharge'}
|
||||||
className={'form-group--select-list'}
|
className={'form-group--select-list'}
|
||||||
label={<T id={'task.dialog.charge'} />}
|
label={<T id={'project_task.dialog.charge'} />}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<TaskModalChargeSelect
|
<ChangeTypesSelect
|
||||||
name="taskCharge"
|
name="taskCharge"
|
||||||
items={modalChargeOptions}
|
items={taskChargeOptions}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
filterable={false}
|
filterable={false}
|
||||||
/>
|
/>
|
||||||
<FInputGroup name="taskamount" />
|
<FInputGroup
|
||||||
|
name="taskamount"
|
||||||
|
disabled={values?.taskCharge === 'Non-chargeable'}
|
||||||
|
/>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -59,21 +62,22 @@ function TaskFormFields() {
|
|||||||
{/*------------ Estimated Amount -----------*/}
|
{/*------------ Estimated Amount -----------*/}
|
||||||
<EstimatedAmountBase>
|
<EstimatedAmountBase>
|
||||||
<EstimatedAmountContent>
|
<EstimatedAmountContent>
|
||||||
<T id={'task.dialog.estimated_amount'} />
|
<T id={'project_task.dialog.estimated_amount'} />
|
||||||
<EstimateAmount>$100000</EstimateAmount>
|
<EstimateAmount>0.00</EstimateAmount>
|
||||||
</EstimatedAmountContent>
|
</EstimatedAmountContent>
|
||||||
</EstimatedAmountBase>
|
</EstimatedAmountBase>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TaskFormFields;
|
export default ProjectTaskFormFields;
|
||||||
|
|
||||||
const EstimatedAmountBase = styled.div`
|
const EstimatedAmountBase = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
font-size: 12px;
|
font-size: 14px;
|
||||||
/* opacity: 0.7; */
|
line-height: 1.5rem;
|
||||||
|
opacity: 0.75;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EstimatedAmountContent = styled.span`
|
const EstimatedAmountContent = styled.span`
|
||||||
@@ -82,7 +86,7 @@ const EstimatedAmountContent = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const EstimateAmount = styled.span`
|
const EstimateAmount = styled.span`
|
||||||
font-size: 13px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import { useTaskFormContext } from './TaskFormProvider';
|
import { useProjectTaskFormContext } from './ProjectTaskFormProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ import { compose } from 'utils';
|
|||||||
* Task form floating actions.
|
* Task form floating actions.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TaskFormFloatingActions({
|
function ProjectTaskFormFloatingActions({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
@@ -19,7 +19,7 @@ function TaskFormFloatingActions({
|
|||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
|
|
||||||
// Task form dialog context.
|
// Task form dialog context.
|
||||||
const { dialogName } = useTaskFormContext();
|
const { dialogName } = useProjectTaskFormContext();
|
||||||
|
|
||||||
// Handle close button click.
|
// Handle close button click.
|
||||||
const handleCancelBtnClick = () => {
|
const handleCancelBtnClick = () => {
|
||||||
@@ -45,4 +45,4 @@ function TaskFormFloatingActions({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(TaskFormFloatingActions);
|
export default compose(withDialogActions)(ProjectTaskFormFloatingActions);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
|
const ProjectTaskFormContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project task form provider.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectTaskFormProvider({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
taskId,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
// State provider.
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<ProjectTaskFormContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useProjectTaskFormContext = () =>
|
||||||
|
React.useContext(ProjectTaskFormContext);
|
||||||
|
|
||||||
|
export { ProjectTaskFormProvider, useProjectTaskFormContext };
|
||||||
@@ -5,30 +5,32 @@ import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
|
|||||||
import withDialogRedux from 'components/DialogReduxConnect';
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const TaskFormDialogContent = React.lazy(
|
const ProjectTaskFormDialogContent = React.lazy(
|
||||||
() => import('./TaskFormDialogContent'),
|
() => import('./ProjectTaskFormDialogContent'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Task form dialog.
|
* Project task form dialog.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TaskFormDialog({ dialogName, payload: { taskId = null }, isOpen }) {
|
function ProjectTaskFormDialog({
|
||||||
|
dialogName,
|
||||||
|
payload: { taskId = null },
|
||||||
|
isOpen,
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
title={intl.get('task.dialog.new_task')}
|
title={intl.get('project_task.dialog.new_task')}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
canEscapeKeyClose={true}
|
canEscapeKeyClose={true}
|
||||||
style={{ width: '500px' }}
|
style={{ width: '500px' }}
|
||||||
>
|
>
|
||||||
<DialogSuspense>
|
<DialogSuspense>
|
||||||
<TaskFormDialogContent dialogName={dialogName} task={taskId} />
|
<ProjectTaskFormDialogContent dialogName={dialogName} task={taskId} />
|
||||||
</DialogSuspense>
|
</DialogSuspense>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default compose(withDialogRedux())(TaskFormDialog);
|
export default compose(withDialogRedux())(ProjectTaskFormDialog);
|
||||||
|
|
||||||
const TaskFormDialogRoot = styled(Dialog)``;
|
|
||||||
@@ -3,17 +3,19 @@ import intl from 'react-intl-universal';
|
|||||||
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
import { DATATYPES_LENGTH } from 'common/dataTypes';
|
||||||
|
|
||||||
const Schema = Yup.object().shape({
|
const Schema = Yup.object().shape({
|
||||||
date: Yup.date().label(intl.get('time_entry.schema.label.date')).required(),
|
date: Yup.date()
|
||||||
|
.label(intl.get('project_time_entry.schema.label.date'))
|
||||||
|
.required(),
|
||||||
projectId: Yup.string()
|
projectId: Yup.string()
|
||||||
.label(intl.get('time_entry.schema.label.project_name'))
|
.label(intl.get('project_time_entry.schema.label.project_name'))
|
||||||
.required(),
|
.required(),
|
||||||
taskId: Yup.string()
|
taskId: Yup.string()
|
||||||
.label(intl.get('time_entry.schema.label.task_name'))
|
.label(intl.get('project_time_entry.schema.label.task_name'))
|
||||||
.required(),
|
.required(),
|
||||||
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
||||||
duration: Yup.string()
|
duration: Yup.string()
|
||||||
.label(intl.get('time_entry.schema.label.duration'))
|
.label(intl.get('project_time_entry.schema.label.duration'))
|
||||||
.required(),
|
.required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const CreateTimeEntryFormSchema = Schema;
|
export const CreateProjectTimeEntryFormSchema = Schema;
|
||||||
@@ -5,9 +5,9 @@ import intl from 'react-intl-universal';
|
|||||||
import { Formik } from 'formik';
|
import { Formik } from 'formik';
|
||||||
import { AppToaster } from 'components';
|
import { AppToaster } from 'components';
|
||||||
|
|
||||||
import TimeEntryFormContent from './TimeEntryFormContent';
|
import ProjectTimeEntryFormContent from './ProjectTimeEntryFormContent';
|
||||||
import { CreateTimeEntryFormSchema } from './TimeEntryForm.schema';
|
import { CreateProjectTimeEntryFormSchema } from './ProjectTimeEntryForm.schema';
|
||||||
import { useTimeEntryFormContext } from './TimeEntryFormProvider';
|
import { useProjectTimeEntryFormContext } from './ProjectTimeEntryFormProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
@@ -21,15 +21,15 @@ const defaultInitialValues = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time entry form.
|
* Project Time entry form.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TimeEntryForm({
|
function ProjectTimeEntryForm({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// time entry form dialog context.
|
// time entry form dialog context.
|
||||||
const { dialogName } = useTimeEntryFormContext();
|
const { dialogName } = useProjectTimeEntryFormContext();
|
||||||
|
|
||||||
// Initial form values
|
// Initial form values
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
@@ -58,12 +58,12 @@ function TimeEntryForm({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Formik
|
||||||
validationSchema={CreateTimeEntryFormSchema}
|
validationSchema={CreateProjectTimeEntryFormSchema}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
onSubmit={handleFormSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
component={TimeEntryFormContent}
|
component={ProjectTimeEntryFormContent}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(TimeEntryForm);
|
export default compose(withDialogActions)(ProjectTimeEntryForm);
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Form } from 'formik';
|
||||||
|
import ProjectTimeEntryFormFields from './ProjectTimeEntryFormFields';
|
||||||
|
import ProjectTimeEntryFormFloatingActions from './ProjectTimeEntryFormFloatingActions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Time entry form content.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export default function TimeEntryFormContent() {
|
||||||
|
return (
|
||||||
|
<Form>
|
||||||
|
<ProjectTimeEntryFormFields />
|
||||||
|
<ProjectTimeEntryFormFloatingActions />
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { ProjectTimeEntryFormProvider } from './ProjectTimeEntryFormProvider';
|
||||||
|
import ProjectTimeEntryForm from './ProjectTimeEntryForm';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project time entry form dialog content.
|
||||||
|
* @returns {ReactNode}
|
||||||
|
*/
|
||||||
|
export default function ProjectTimeEntryFormDialogContent({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
project,
|
||||||
|
timeEntry,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ProjectTimeEntryFormProvider
|
||||||
|
projectId={project}
|
||||||
|
timeEntryId={timeEntry}
|
||||||
|
dialogName={dialogName}
|
||||||
|
>
|
||||||
|
<ProjectTimeEntryForm />
|
||||||
|
</ProjectTimeEntryFormProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -14,25 +14,25 @@ import {
|
|||||||
FieldRequiredHint,
|
FieldRequiredHint,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
} from 'components';
|
} from 'components';
|
||||||
import { ProjectSelect, TaskSelect } from './components';
|
import { TaskSelect, ProjectsSelect } from '../../components';
|
||||||
import { momentFormatter } from 'utils';
|
import { momentFormatter } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time entry form fields.
|
* Project time entry form fields.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TimeEntryFormFields() {
|
function ProjectTimeEntryFormFields() {
|
||||||
return (
|
return (
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
{/*------------ Project -----------*/}
|
{/*------------ Project -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'projectId'}
|
name={'projectId'}
|
||||||
label={<T id={'time_entry.dialog.project'} />}
|
label={<T id={'project_time_entry.dialog.project'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
>
|
>
|
||||||
<ProjectSelect
|
<ProjectsSelect
|
||||||
name={'tesc'}
|
name={'projectId'}
|
||||||
projects={[]}
|
projects={[]}
|
||||||
popoverProps={{ minimal: true }}
|
popoverProps={{ minimal: true }}
|
||||||
/>
|
/>
|
||||||
@@ -40,7 +40,7 @@ function TimeEntryFormFields() {
|
|||||||
{/*------------ Task -----------*/}
|
{/*------------ Task -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'taskId'}
|
name={'taskId'}
|
||||||
label={<T id={'time_entry.dialog.task'} />}
|
label={<T id={'project_time_entry.dialog.task'} />}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
className={classNames('form-group--select-list', Classes.FILL)}
|
className={classNames('form-group--select-list', Classes.FILL)}
|
||||||
>
|
>
|
||||||
@@ -53,7 +53,7 @@ function TimeEntryFormFields() {
|
|||||||
|
|
||||||
{/*------------ Duration -----------*/}
|
{/*------------ Duration -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={intl.get('time_entry.dialog.duration')}
|
label={intl.get('project_time_entry.dialog.duration')}
|
||||||
name={'duration'}
|
name={'duration'}
|
||||||
labelInfo={<FieldRequiredHint />}
|
labelInfo={<FieldRequiredHint />}
|
||||||
>
|
>
|
||||||
@@ -61,7 +61,7 @@ function TimeEntryFormFields() {
|
|||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
{/*------------ Date -----------*/}
|
{/*------------ Date -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
label={intl.get('time_entry.dialog.date')}
|
label={intl.get('project_time_entry.dialog.date')}
|
||||||
name={'date'}
|
name={'date'}
|
||||||
className={classNames(CLASSES.FILL, 'form-group--date')}
|
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||||
>
|
>
|
||||||
@@ -78,20 +78,13 @@ function TimeEntryFormFields() {
|
|||||||
{/*------------ Description -----------*/}
|
{/*------------ Description -----------*/}
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'description'}
|
name={'description'}
|
||||||
label={intl.get('time_entry.dialog.description')}
|
label={intl.get('project_time_entry.dialog.description')}
|
||||||
className={'form-group--description'}
|
className={'form-group--description'}
|
||||||
>
|
>
|
||||||
<FTextArea name={'description'} />
|
<FTextArea name={'description'} />
|
||||||
{/* <FEditableText
|
|
||||||
multiline={true}
|
|
||||||
// minLines={1.78}
|
|
||||||
// maxLines={1.78}
|
|
||||||
name={'description'}
|
|
||||||
placeholder=""
|
|
||||||
/> */}
|
|
||||||
</FFormGroup>
|
</FFormGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TimeEntryFormFields;
|
export default ProjectTimeEntryFormFields;
|
||||||
@@ -3,20 +3,20 @@ import React from 'react';
|
|||||||
import { useFormikContext } from 'formik';
|
import { useFormikContext } from 'formik';
|
||||||
import { Intent, Button, Classes } from '@blueprintjs/core';
|
import { Intent, Button, Classes } from '@blueprintjs/core';
|
||||||
import { FormattedMessage as T } from 'components';
|
import { FormattedMessage as T } from 'components';
|
||||||
import { useTimeEntryFormContext } from './TimeEntryFormProvider';
|
import { useProjectTimeEntryFormContext } from './ProjectTimeEntryFormProvider';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time entry form floating actions.
|
* Projcet time entry form floating actions.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TimeEntryFormFloatingActions({
|
function ProjectTimeEntryFormFloatingActions({
|
||||||
// #withDialogActions
|
// #withDialogActions
|
||||||
closeDialog,
|
closeDialog,
|
||||||
}) {
|
}) {
|
||||||
// time entry form dialog context.
|
// time entry form dialog context.
|
||||||
const { dialogName } = useTimeEntryFormContext();
|
const { dialogName } = useProjectTimeEntryFormContext();
|
||||||
|
|
||||||
// Formik context.
|
// Formik context.
|
||||||
const { isSubmitting } = useFormikContext();
|
const { isSubmitting } = useFormikContext();
|
||||||
@@ -45,4 +45,4 @@ function TimeEntryFormFloatingActions({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogActions)(TimeEntryFormFloatingActions);
|
export default compose(withDialogActions)(ProjectTimeEntryFormFloatingActions);
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import React from 'react';
|
||||||
|
import { DialogContent } from 'components';
|
||||||
|
|
||||||
|
const ProjecctTimeEntryFormContext = React.createContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project time entry form provider.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectTimeEntryFormProvider({
|
||||||
|
// #ownProps
|
||||||
|
dialogName,
|
||||||
|
projectId,
|
||||||
|
timeEntryId,
|
||||||
|
...props
|
||||||
|
}) {
|
||||||
|
const provider = {
|
||||||
|
dialogName,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogContent>
|
||||||
|
<ProjecctTimeEntryFormContext.Provider value={provider} {...props} />
|
||||||
|
</DialogContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useProjectTimeEntryFormContext = () =>
|
||||||
|
React.useContext(ProjecctTimeEntryFormContext);
|
||||||
|
|
||||||
|
export { ProjectTimeEntryFormProvider, useProjectTimeEntryFormContext };
|
||||||
@@ -4,42 +4,42 @@ import { Dialog, DialogSuspense, FormattedMessage as T } from 'components';
|
|||||||
import withDialogRedux from 'components/DialogReduxConnect';
|
import withDialogRedux from 'components/DialogReduxConnect';
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const TimeEntryFormDialogContent = React.lazy(
|
const ProjectTimeEntryFormDialogContent = React.lazy(
|
||||||
() => import('./TimeEntryFormDialogContent'),
|
() => import('./ProjectTimeEntryFormDialogContent'),
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Time entry form dialog.
|
* Project time entry form dialog.
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
function TimeEntryFormDialog({
|
function ProjectTimeEntryFormDialog({
|
||||||
dialogName,
|
dialogName,
|
||||||
isOpen,
|
isOpen,
|
||||||
payload: { projectId = null, timeEntryId = null },
|
payload: { projectId = null, timeEntryId = null },
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<TimeEntryFormDialogRoot
|
<ProjectTimeEntryFormDialogRoot
|
||||||
name={dialogName}
|
name={dialogName}
|
||||||
title={<T id={'time_entry.dialog.label'} />}
|
title={<T id={'project_time_entry.dialog.label'} />}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
canEscapeKeyClose={true}
|
canEscapeKeyClose={true}
|
||||||
style={{ width: '400px' }}
|
style={{ width: '400px' }}
|
||||||
>
|
>
|
||||||
<DialogSuspense>
|
<DialogSuspense>
|
||||||
<TimeEntryFormDialogContent
|
<ProjectTimeEntryFormDialogContent
|
||||||
dialogName={dialogName}
|
dialogName={dialogName}
|
||||||
project={projectId}
|
project={projectId}
|
||||||
timeEntry={timeEntryId}
|
timeEntry={timeEntryId}
|
||||||
/>
|
/>
|
||||||
</DialogSuspense>
|
</DialogSuspense>
|
||||||
</TimeEntryFormDialogRoot>
|
</ProjectTimeEntryFormDialogRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default compose(withDialogRedux())(TimeEntryFormDialog);
|
export default compose(withDialogRedux())(ProjectTimeEntryFormDialog);
|
||||||
|
|
||||||
const TimeEntryFormDialogRoot = styled(Dialog)`
|
const ProjectTimeEntryFormDialogRoot = styled(Dialog)`
|
||||||
.bp3-dialog-body {
|
.bp3-dialog-body {
|
||||||
.bp3-form-group {
|
.bp3-form-group {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
//@ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
@@ -5,51 +6,17 @@ import { DataTable } from 'components';
|
|||||||
import { TABLES } from 'common/tables';
|
import { TABLES } from 'common/tables';
|
||||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||||
|
import ProjectsEmptyStatus from './ProjectsEmptyStatus';
|
||||||
import { useProjectsListContext } from './ProjectsListProvider';
|
import { useProjectsListContext } from './ProjectsListProvider';
|
||||||
import { useMemorizedColumnsWidths } from 'hooks';
|
import { useMemorizedColumnsWidths } from 'hooks';
|
||||||
import { useProjectsListColumns, ActionsMenu } from './components';
|
import { useProjectsListColumns, ActionsMenu } from './components';
|
||||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
import withAlertsActions from 'containers/Alert/withAlertActions';
|
||||||
import withProjectsActions from './withProjectsActions';
|
import withProjectsActions from './withProjectsActions';
|
||||||
import withSettings from '../../../Settings/withSettings';
|
import withSettings from '../../../Settings/withSettings';
|
||||||
|
|
||||||
import { compose } from 'utils';
|
import { compose } from 'utils';
|
||||||
|
|
||||||
const projects = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
name: 'Maroon Bronze',
|
|
||||||
deadline: '2022-06-08T22:00:00.000Z',
|
|
||||||
display_name: 'Kyrie Rearden',
|
|
||||||
cost_estimate: '40000',
|
|
||||||
task_amount: '0',
|
|
||||||
is_process: true,
|
|
||||||
is_closed: false,
|
|
||||||
is_draft: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
name: 'Project Sherwood',
|
|
||||||
deadline: '2022-06-08T22:00:00.000Z',
|
|
||||||
display_name: 'Ella-Grace Miller',
|
|
||||||
cost_estimate: '700',
|
|
||||||
task_amount: '300',
|
|
||||||
is_process: false,
|
|
||||||
is_closed: false,
|
|
||||||
is_draft: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
name: 'Tax Compliance',
|
|
||||||
deadline: '2022-06-23T22:00:00.000Z',
|
|
||||||
display_name: 'Abby & Wells',
|
|
||||||
cost_estimate: '3000',
|
|
||||||
task_amount: '0',
|
|
||||||
is_process: true,
|
|
||||||
is_closed: false,
|
|
||||||
is_draft: false,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Projects list datatable.
|
* Projects list datatable.
|
||||||
* @returns
|
* @returns
|
||||||
@@ -58,11 +25,23 @@ function ProjectsDataTable({
|
|||||||
// #withDial
|
// #withDial
|
||||||
openDialog,
|
openDialog,
|
||||||
|
|
||||||
|
// #withAlertsActions
|
||||||
|
openAlert,
|
||||||
|
|
||||||
// #withSettings
|
// #withSettings
|
||||||
projectsTableSize,
|
projectsTableSize,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|
||||||
|
// Projects list context.
|
||||||
|
const { projects, isEmptyStatus, isProjectsLoading, isProjectsFetching } =
|
||||||
|
useProjectsListContext();
|
||||||
|
|
||||||
|
// Handle delete project.
|
||||||
|
const handleDeleteProject = ({ id }) => {
|
||||||
|
openAlert('project-delete', { projectId: id });
|
||||||
|
};
|
||||||
|
|
||||||
// Retrieve projects table columns.
|
// Retrieve projects table columns.
|
||||||
const columns = useProjectsListColumns();
|
const columns = useProjectsListColumns();
|
||||||
|
|
||||||
@@ -78,12 +57,13 @@ function ProjectsDataTable({
|
|||||||
const handleEditProject = (project) => {
|
const handleEditProject = (project) => {
|
||||||
openDialog('project-form', {
|
openDialog('project-form', {
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
|
action: 'edit',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle new task button click.
|
// Handle new task button click.
|
||||||
const handleNewTaskButtonClick = () => {
|
const handleNewTaskButtonClick = () => {
|
||||||
openDialog('task-form');
|
openDialog('project-task-form');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local storage memorizing columns widths.
|
// Local storage memorizing columns widths.
|
||||||
@@ -98,13 +78,18 @@ function ProjectsDataTable({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Display project empty status instead of the table.
|
||||||
|
if (isEmptyStatus) {
|
||||||
|
return <ProjectsEmptyStatus />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProjectsTable
|
<ProjectsTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={projects}
|
data={projects}
|
||||||
// loading={}
|
loading={isProjectsLoading}
|
||||||
// headerLoading={}
|
headerLoading={isProjectsLoading}
|
||||||
// progressBarLoading={}
|
progressBarLoading={isProjectsFetching}
|
||||||
manualSortBy={true}
|
manualSortBy={true}
|
||||||
noInitialFetch={true}
|
noInitialFetch={true}
|
||||||
sticky={true}
|
sticky={true}
|
||||||
@@ -119,6 +104,7 @@ function ProjectsDataTable({
|
|||||||
payload={{
|
payload={{
|
||||||
onViewDetails: handleViewDetailProject,
|
onViewDetails: handleViewDetailProject,
|
||||||
onEdit: handleEditProject,
|
onEdit: handleEditProject,
|
||||||
|
onDelete: handleDeleteProject,
|
||||||
onNewTask: handleNewTaskButtonClick,
|
onNewTask: handleNewTaskButtonClick,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -127,6 +113,7 @@ function ProjectsDataTable({
|
|||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withDialogActions,
|
withDialogActions,
|
||||||
|
withAlertsActions,
|
||||||
withProjectsActions,
|
withProjectsActions,
|
||||||
withSettings(({ projectSettings }) => ({
|
withSettings(({ projectSettings }) => ({
|
||||||
projectsTableSize: projectSettings?.tableSize,
|
projectsTableSize: projectSettings?.tableSize,
|
||||||
@@ -138,27 +125,28 @@ const ProjectsTable = styled(DataTable)`
|
|||||||
.tr .td {
|
.tr .td {
|
||||||
padding: 0.5rem 0.8rem;
|
padding: 0.5rem 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar.td {
|
.avatar.td {
|
||||||
.avatar {
|
.cell-inner {
|
||||||
display: inline-block;
|
.avatar {
|
||||||
background: #adbcc9;
|
display: inline-block;
|
||||||
border-radius: 8%;
|
background: #adbcc9;
|
||||||
text-align: center;
|
border-radius: 8%;
|
||||||
font-weight: 400;
|
text-align: center;
|
||||||
color: #fff;
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
&[data-size='medium'] {
|
&[data-size='medium'] {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
line-height: 30px;
|
line-height: 30px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
&[data-size='small'] {
|
&[data-size='small'] {
|
||||||
height: 25px;
|
height: 25px;
|
||||||
width: 25px;
|
width: 25px;
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Button, Intent } from '@blueprintjs/core';
|
||||||
|
import { EmptyStatus, FormattedMessage as T } from 'components';
|
||||||
|
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||||
|
|
||||||
|
import { compose } from 'utils';
|
||||||
|
|
||||||
|
function ProjectsEmptyStatus({
|
||||||
|
// #withDialogActions
|
||||||
|
openDialog,
|
||||||
|
}) {
|
||||||
|
// Handle new project button click.
|
||||||
|
const handleNewProjectClick = () => {
|
||||||
|
openDialog('project-form', {});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EmptyStatus
|
||||||
|
title={<T id="projects.empty_status.title" />}
|
||||||
|
description={
|
||||||
|
<p>
|
||||||
|
<T id="projects.empty_status.description" />
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
action={
|
||||||
|
<React.Fragment>
|
||||||
|
<Button
|
||||||
|
intent={Intent.PRIMARY}
|
||||||
|
large={true}
|
||||||
|
onClick={handleNewProjectClick}
|
||||||
|
>
|
||||||
|
<T id="projects.empty_status.action" />
|
||||||
|
</Button>
|
||||||
|
<Button intent={Intent.NONE} large={true}>
|
||||||
|
<T id={'learn_more'} />
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(withDialogActions)(ProjectsEmptyStatus);
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
//@ts-nocheck
|
//@ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import { useResourceViews, useResourceMeta } from 'hooks/query';
|
import { useResourceViews, useResourceMeta } from 'hooks/query';
|
||||||
import DashboardInsider from '../../../../components/Dashboard/DashboardInsider';
|
import DashboardInsider from '../../../../components/Dashboard/DashboardInsider';
|
||||||
|
import { useProjects } from '../../hooks';
|
||||||
|
|
||||||
const ProjectsListContext = React.createContext();
|
const ProjectsListContext = React.createContext();
|
||||||
|
|
||||||
@@ -14,16 +16,32 @@ function ProjectsListProvider({ query, tableStateChanged, ...props }) {
|
|||||||
const { data: projectsViews, isLoading: isViewsLoading } =
|
const { data: projectsViews, isLoading: isViewsLoading } =
|
||||||
useResourceViews('projects');
|
useResourceViews('projects');
|
||||||
|
|
||||||
|
// Fetch accounts list according to the given custom view id.
|
||||||
|
const {
|
||||||
|
data: { projects },
|
||||||
|
isFetching: isProjectsFetching,
|
||||||
|
isLoading: isProjectsLoading,
|
||||||
|
} = useProjects(query, { keepPreviousData: true });
|
||||||
|
|
||||||
|
// Detarmines the datatable empty status.
|
||||||
|
const isEmptyStatus =
|
||||||
|
isEmpty(projects) && !tableStateChanged && !isProjectsLoading;
|
||||||
|
|
||||||
// provider payload.
|
// provider payload.
|
||||||
const provider = {
|
const provider = {
|
||||||
|
projects,
|
||||||
|
|
||||||
projectsViews,
|
projectsViews,
|
||||||
|
|
||||||
|
isProjectsLoading,
|
||||||
|
isProjectsFetching,
|
||||||
|
isViewsLoading,
|
||||||
|
|
||||||
|
isEmptyStatus,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider
|
<DashboardInsider loading={isViewsLoading} name={'projects'}>
|
||||||
// loading={isViewsLoading}
|
|
||||||
name={'projects'}
|
|
||||||
>
|
|
||||||
<ProjectsListContext.Provider value={provider} {...props} />
|
<ProjectsListContext.Provider value={provider} {...props} />
|
||||||
</DashboardInsider>
|
</DashboardInsider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,13 +102,15 @@ export const ActionsMenu = ({
|
|||||||
export const ProjectsAccessor = (row) => (
|
export const ProjectsAccessor = (row) => (
|
||||||
<ProjectItemsWrap>
|
<ProjectItemsWrap>
|
||||||
<ProjectItemsHeader>
|
<ProjectItemsHeader>
|
||||||
<ProjectItemContactName>{row.display_name}</ProjectItemContactName>
|
<ProjectItemContactName>
|
||||||
|
{row.contact_display_name}
|
||||||
|
</ProjectItemContactName>
|
||||||
<ProjectItemProjectName>{row.name}</ProjectItemProjectName>
|
<ProjectItemProjectName>{row.name}</ProjectItemProjectName>
|
||||||
</ProjectItemsHeader>
|
</ProjectItemsHeader>
|
||||||
<ProjectItemDescription>
|
<ProjectItemDescription>
|
||||||
<FormatDate value={row.deadline} />
|
<FormatDate value={row.deadline_formatted} />
|
||||||
{intl.get('projects.label.cost_estimate', {
|
{intl.get('projects.label.cost_estimate', {
|
||||||
value: row.cost_estimate,
|
value: row.cost_estimate_formatted,
|
||||||
})}
|
})}
|
||||||
</ProjectItemDescription>
|
</ProjectItemDescription>
|
||||||
</ProjectItemsWrap>
|
</ProjectItemsWrap>
|
||||||
@@ -175,10 +177,10 @@ const ProjectItemDescription = styled.div`
|
|||||||
const ProjectStatusRoot = styled.div`
|
const ProjectStatusRoot = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
/* justify-content: flex-end; */
|
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
flex-direction: row-reverse;
|
flex-direction: row-reverse;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ProjectStatusTaskAmount = styled.div`
|
const ProjectStatusTaskAmount = styled.div`
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
@@ -198,6 +200,7 @@ const ProjectProgressBar = styled(ProgressBar)`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const StatusTag = styled(Tag)`
|
const StatusTag = styled(Tag)`
|
||||||
min-width: 65px;
|
min-width: 65px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form } from 'formik';
|
|
||||||
import TaskFormFields from './TaskFormFields';
|
|
||||||
import TaskFormFloatingActions from './TaskFormFloatingActions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task form content.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export default function TaskFormContent() {
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<TaskFormFields />
|
|
||||||
<TaskFormFloatingActions />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TaskFormProvider } from './TaskFormProvider';
|
|
||||||
import TaskForm from './TaskForm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task form dialog content.
|
|
||||||
*/
|
|
||||||
export default function TaskFormDialogContent({
|
|
||||||
// #ownProps
|
|
||||||
dialogName,
|
|
||||||
task,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<TaskFormProvider taskId={task} dialogName={dialogName}>
|
|
||||||
<TaskForm />
|
|
||||||
</TaskFormProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
//@ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { DialogContent } from 'components';
|
|
||||||
|
|
||||||
const TaskFormContext = React.createContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Task form provider.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function TaskFormProvider({
|
|
||||||
// #ownProps
|
|
||||||
dialogName,
|
|
||||||
taskId,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
// State provider.
|
|
||||||
const provider = {
|
|
||||||
dialogName,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogContent>
|
|
||||||
<TaskFormContext.Provider value={provider} {...props} />
|
|
||||||
</DialogContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useTaskFormContext = () => React.useContext(TaskFormContext);
|
|
||||||
|
|
||||||
export { TaskFormProvider, useTaskFormContext };
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form } from 'formik';
|
|
||||||
import TimeEntryFormFields from './TimeEntryFormFields';
|
|
||||||
import TimeEntryFormFloatingActions from './TimeEntryFormFloatingActions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time entry form content.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export default function TimeEntryFormContent() {
|
|
||||||
return (
|
|
||||||
<Form>
|
|
||||||
<TimeEntryFormFields />
|
|
||||||
<TimeEntryFormFloatingActions />
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { TimeEntryFormProvider } from './TimeEntryFormProvider';
|
|
||||||
import TimeEntryForm from './TimeEntryForm';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time entry form dialog content.
|
|
||||||
* @returns {ReactNode}
|
|
||||||
*/
|
|
||||||
export default function TimeEntryFormDialogContent({
|
|
||||||
// #ownProps
|
|
||||||
dialogName,
|
|
||||||
project,
|
|
||||||
timeEntry,
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<TimeEntryFormProvider
|
|
||||||
projectId={project}
|
|
||||||
timeEntryId={timeEntry}
|
|
||||||
dialogName={dialogName}
|
|
||||||
>
|
|
||||||
<TimeEntryForm />
|
|
||||||
</TimeEntryFormProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
//@ts-nocheck
|
|
||||||
import React from 'react';
|
|
||||||
import { DialogContent } from 'components';
|
|
||||||
|
|
||||||
const TimeEntryFormContext = React.createContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time entry form provider.
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
function TimeEntryFormProvider({
|
|
||||||
// #ownProps
|
|
||||||
dialogName,
|
|
||||||
projectId,
|
|
||||||
timeEntryId,
|
|
||||||
...props
|
|
||||||
}) {
|
|
||||||
const provider = {
|
|
||||||
dialogName,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DialogContent>
|
|
||||||
<TimeEntryFormContext.Provider value={provider} {...props} />
|
|
||||||
</DialogContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const useTimeEntryFormContext = () => React.useContext(TimeEntryFormContext);
|
|
||||||
|
|
||||||
export { TimeEntryFormProvider, useTimeEntryFormContext };
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import intl from 'react-intl-universal';
|
|
||||||
import { MenuItem, Button } from '@blueprintjs/core';
|
|
||||||
import { FSelect } from '../../../../../components/Forms';
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} query
|
|
||||||
* @param {*} project
|
|
||||||
* @param {*} _index
|
|
||||||
* @param {*} exactMatch
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const projectItemPredicate = (query, project, _index, exactMatch) => {
|
|
||||||
const normalizedTitle = project.name.toLowerCase();
|
|
||||||
const normalizedQuery = query.toLowerCase();
|
|
||||||
|
|
||||||
if (exactMatch) {
|
|
||||||
return normalizedTitle === normalizedQuery;
|
|
||||||
} else {
|
|
||||||
return `${project.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} project
|
|
||||||
* @param {*} param1
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const projectItemRenderer = (project, { handleClick, modifiers, query }) => {
|
|
||||||
return (
|
|
||||||
<MenuItem
|
|
||||||
active={modifiers.active}
|
|
||||||
disabled={modifiers.disabled}
|
|
||||||
key={project.id}
|
|
||||||
onClick={handleClick}
|
|
||||||
text={project.name}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const projectSelectProps = {
|
|
||||||
// itemPredicate: projectItemPredicate,
|
|
||||||
itemRenderer: projectItemRenderer,
|
|
||||||
valueAccessor: 'id',
|
|
||||||
labelAccessor: 'name',
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ProjectSelect({ projects, ...rest }) {
|
|
||||||
return (
|
|
||||||
<FSelect
|
|
||||||
items={projects}
|
|
||||||
{...projectSelectProps}
|
|
||||||
{...rest}
|
|
||||||
input={ProjectSelectButton}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ProjectSelectButton({ label }) {
|
|
||||||
return <Button text={label ? label : intl.get('find_or_choose_a_project')} />;
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export * from './ProjectSelect';
|
|
||||||
export * from './TaskSelect';
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
|
export const taskChargeOptions = [
|
||||||
|
{ name: intl.get('task.dialog.hourly_rate'), value: 'hourly_rate' },
|
||||||
|
{ name: intl.get('task.dialog.fixed_price'), value: 'fixed_price' },
|
||||||
|
{ name: intl.get('task.dialog.non_chargeable'), value: 'non_chargeable' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const expenseChargeOption = [
|
||||||
|
{
|
||||||
|
name: intl.get('expenses.dialog.markup'),
|
||||||
|
value: 'markup',
|
||||||
|
},
|
||||||
|
{ name: intl.get('expenses.dialog.pass_cost_on'), value: 'pass_cost_on' },
|
||||||
|
{ name: intl.get('expenses.dialog.custom_pirce'), value: 'custom_pirce' },
|
||||||
|
{ name: intl.get('expenses.dialog.non_chargeable'), value: 'non_chargeable' },
|
||||||
|
];
|
||||||
125
src/containers/Projects/hooks/index.ts
Normal file
125
src/containers/Projects/hooks/index.ts
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
//@ts-nocheck
|
||||||
|
import { useQueryClient, useMutation } from 'react-query';
|
||||||
|
import { useRequestQuery } from 'hooks/useQueryRequest';
|
||||||
|
import { transformPagination } from 'utils';
|
||||||
|
import useApiRequest from 'hooks/useRequest';
|
||||||
|
import t from './type';
|
||||||
|
|
||||||
|
// Common invalidate queries.
|
||||||
|
const commonInvalidateQueries = (queryClient) => {
|
||||||
|
// Invalidate projects.
|
||||||
|
queryClient.invalidateQueries(t.PROJECT);
|
||||||
|
queryClient.invalidateQueries(t.PROJECTS);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new project
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export function useCreateProject(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation((values) => apiRequest.post('projects', values), {
|
||||||
|
onSuccess: (res, values) => {
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Edit the given project
|
||||||
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useEditProject(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
([id, values]) => apiRequest.post(`/projects/${id}`, values),
|
||||||
|
{
|
||||||
|
onSuccess: (res, [id, values]) => {
|
||||||
|
// Invalidate specific project.
|
||||||
|
queryClient.invalidateQueries([t.PROJECT, id]);
|
||||||
|
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete the given project
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export function useDeleteProject(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation((id) => apiRequest.delete(`projects/${id}`), {
|
||||||
|
onSuccess: (res, id) => {
|
||||||
|
// Invalidate specific project.
|
||||||
|
queryClient.invalidateQueries([t.PROJECT, id]);
|
||||||
|
|
||||||
|
// Common invalidate queries.
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the projects details.
|
||||||
|
* @param projectId The project id
|
||||||
|
* @param props
|
||||||
|
* @param requestProps
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useProject(projectId, props, requestProps) {
|
||||||
|
return useRequestQuery(
|
||||||
|
[t.PROJECT, projectId],
|
||||||
|
{ method: 'get', url: `projects/${projectId}`, ...requestProps },
|
||||||
|
{
|
||||||
|
select: (res) => res.data.project,
|
||||||
|
defaultData: {},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformProjects = (res) => ({
|
||||||
|
projects: res.data.projects,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve projects list with pagination meta.
|
||||||
|
* @param query
|
||||||
|
* @param props
|
||||||
|
*/
|
||||||
|
export function useProjects(query, props) {
|
||||||
|
return useRequestQuery(
|
||||||
|
[t.PROJECTS, query],
|
||||||
|
{ method: 'get', url: 'projects', params: query },
|
||||||
|
{
|
||||||
|
select: transformProjects,
|
||||||
|
defaultData: {
|
||||||
|
projects: [],
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRefreshInvoices() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return {
|
||||||
|
refresh: () => {
|
||||||
|
queryClient.invalidateQueries(t.PROJECTS);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
11
src/containers/Projects/hooks/type.ts
Normal file
11
src/containers/Projects/hooks/type.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const CUSTOMERS = {
|
||||||
|
CUSTOMERS: 'CUSTOMERS',
|
||||||
|
CUSTOMER: 'CUSTOMER',
|
||||||
|
};
|
||||||
|
|
||||||
|
const PROJECTS = {
|
||||||
|
PROJECT: 'PROJECT',
|
||||||
|
PROJECTS: 'PROJECTS',
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { ...PROJECTS, ...CUSTOMERS };
|
||||||
@@ -23,7 +23,9 @@ export default (mapState) => {
|
|||||||
vendorsCreditNoteSetting: state.settings.data.vendorCredit,
|
vendorsCreditNoteSetting: state.settings.data.vendorCredit,
|
||||||
warehouseTransferSettings: state.settings.data.warehouseTransfers,
|
warehouseTransferSettings: state.settings.data.warehouseTransfers,
|
||||||
projectSettings:state.settings.data.projects,
|
projectSettings:state.settings.data.projects,
|
||||||
timesheetsSettings:state.settings.data.timesheets
|
timesheetsSettings:state.settings.data.timesheets,
|
||||||
|
purchasesSettings:state.settings.data.purchases,
|
||||||
|
salesSettings:state.settings.data.sales,
|
||||||
};
|
};
|
||||||
return mapState ? mapState(mapped, state, props) : mapped;
|
return mapState ? mapState(mapped, state, props) : mapped;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2057,20 +2057,32 @@
|
|||||||
"projects.dialog.cost_estimate": "Cost Estimate",
|
"projects.dialog.cost_estimate": "Cost Estimate",
|
||||||
"projects.label.create": "Create",
|
"projects.label.create": "Create",
|
||||||
"projects.label.cost_estimate": " • Estimate {value}",
|
"projects.label.cost_estimate": " • Estimate {value}",
|
||||||
"task.dialog.new_task": "New Task",
|
"projects.dialog.success_message": "The project has been created successfully.",
|
||||||
"task.dialog.task_name": "Task Name",
|
"projects.dialog.edit_success_message": "The project has been edited successfully.",
|
||||||
"task.dialog.estimated_hours": "Estimate Hours",
|
"projects.dialog.new_project": "New Project",
|
||||||
"task.dialog.charge": "Charge",
|
"projects.dialog.edit_project": "Edit Project",
|
||||||
"task.dialog.estimated_amount": "Estimated Amount",
|
"projects.alert.delete_message": "The deleted project has been deleted successfully.",
|
||||||
|
"projects.alert.once_delete_this_project": "Once you delete this project, you won't be able to restore it later. Are you sure you want to delete this project?",
|
||||||
|
"projects.empty_status.title":"",
|
||||||
|
"projects.empty_status.description":"",
|
||||||
|
"projects.empty_status.action":"New Project",
|
||||||
|
"project_task.dialog.new_task": "New Task",
|
||||||
|
"project_task.dialog.task_name": "Task Name",
|
||||||
|
"project_task.dialog.estimated_hours": "Estimate Hours",
|
||||||
|
"project_task.dialog.charge": "Charge",
|
||||||
|
"project_task.dialog.estimated_amount": "Estimated Amount",
|
||||||
|
"project_task.dialog.hourly_rate": "Hourly rate",
|
||||||
|
"project_task.dialog.fixed_price": "Fixed price",
|
||||||
|
"project_task.dialog.non_chargeable": "Non-chargeable",
|
||||||
"project.schema.label.contact": "Contact",
|
"project.schema.label.contact": "Contact",
|
||||||
"project.schema.label.project_name": "Project name",
|
"project.schema.label.project_name": "Project name",
|
||||||
"project.schema.label.deadline": "Deadline",
|
"project.schema.label.deadline": "Deadline",
|
||||||
"project.schema.label.project_state": "Project state",
|
"project.schema.label.project_state": "Project state",
|
||||||
"project.schema.label.project_cost": "Project cost",
|
"project.schema.label.project_cost": "Project cost",
|
||||||
"task.schema.label.task_name": "Task name",
|
"project_task.schema.label.task_name": "Task name",
|
||||||
"task.schema.label.task_house": "Task house",
|
"project_task.schema.label.task_house": "Task house",
|
||||||
"task.schema.label.charge": "Charge",
|
"project_task.schema.label.charge": "Charge",
|
||||||
"task.schema.label.amount": "Amount",
|
"project_task.schema.label.amount": "Amount",
|
||||||
"projcet_details.action.new_transaction": "New Transaction",
|
"projcet_details.action.new_transaction": "New Transaction",
|
||||||
"projcet_details.action.time_entry": "Time",
|
"projcet_details.action.time_entry": "Time",
|
||||||
"projcet_details.action.edit_project": "Edit Project",
|
"projcet_details.action.edit_project": "Edit Project",
|
||||||
@@ -2079,23 +2091,75 @@
|
|||||||
"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_expenses": "New Expenses",
|
||||||
|
"project_details.new_estimated_expenses": "New Estimated Expenses",
|
||||||
"timesheets.actions.delete_timesheet": "Delete",
|
"timesheets.actions.delete_timesheet": "Delete",
|
||||||
"timesheets.column.date": "Date",
|
"timesheets.column.date": "Date",
|
||||||
"timesheets.column.task": "Task",
|
"timesheets.column.task": "Task",
|
||||||
"timesheets.column.user": "User",
|
"timesheets.column.user": "User",
|
||||||
"timesheets.column.time": "Time",
|
"timesheets.column.time": "Time",
|
||||||
"timesheets.column.billing_status": "Billing Status",
|
"timesheets.column.billing_status": "Billing Status",
|
||||||
"time_entry.dialog.label": "New Time Entry",
|
"project_time_entry.dialog.label": "New Time Entry",
|
||||||
"time_entry.dialog.project": "Project",
|
"project_time_entry.dialog.project": "Project",
|
||||||
"time_entry.dialog.task": "Task",
|
"project_time_entry.dialog.task": "Task",
|
||||||
"time_entry.dialog.description": "Description",
|
"project_time_entry.dialog.description": "Description",
|
||||||
"time_entry.dialog.duration": "Duration",
|
"project_time_entry.dialog.duration": "Duration",
|
||||||
"time_entry.dialog.date": "Date",
|
"project_time_entry.dialog.date": "Date",
|
||||||
"time_entry.dialog.create": "Create",
|
"project_time_entry.dialog.create": "Create",
|
||||||
"time_entry.schema.label.project_name": "Project name",
|
"project_time_entry.schema.label.project_name": "Project name",
|
||||||
"time_entry.schema.label.task_name": "Task name",
|
"project_time_entry.schema.label.task_name": "Task name",
|
||||||
"time_entry.schema.label.duration": "Duration",
|
"project_time_entry.schema.label.duration": "Duration",
|
||||||
"time_entry.schema.label.date": "Date",
|
"project_time_entry.schema.label.date": "Date",
|
||||||
"find_or_choose_a_project": "Find or choose a project",
|
"find_or_choose_a_project": "Find or choose a project",
|
||||||
"choose_a_task": "Choose a task"
|
"choose_a_task": "Choose a task",
|
||||||
|
"project_expense.dialog.label": "New Expense",
|
||||||
|
"project_expense.dialog.expense_name": "Expense Name",
|
||||||
|
"project_expense.dialog.expense_date": "Date",
|
||||||
|
"project_expense.dialog.quantity": "Quantity",
|
||||||
|
"project_expense.dialog.charge": "Charge",
|
||||||
|
"project_expense.dialog.track_expense": "Track to Expense",
|
||||||
|
"project_expense.dialog.unit_price": "Unit Price",
|
||||||
|
"project_expense.dialog.expense_total": "Total",
|
||||||
|
"project_expense.dialog.percentage": "Percentage",
|
||||||
|
"project_expense.dialog.total": "Total:",
|
||||||
|
"project_expense.dialog.markup": "% Markup",
|
||||||
|
"project_expense.dialog.pass_cost_on": "Pass cost on",
|
||||||
|
"project_expense.dialog.custom_pirce": "Custom Pirce",
|
||||||
|
"project_expense.dialog.non_chargeable": "Non-chargeable",
|
||||||
|
"project_expense.dialog.cost_to_you": "Cost to you",
|
||||||
|
"project_expense.dialog.what_you_ll_charge": "What you'll charge",
|
||||||
|
"project_expense.schema.label.expense_name": "Expense name",
|
||||||
|
"project_expense.schema.label.estimated_expense": "Estimated expense",
|
||||||
|
"project_expense.schema.label.quantity": "Quantity",
|
||||||
|
"project_expense.schema.label.unit_price": "Unit price",
|
||||||
|
"choose_an_estimated_expense": "Choose an estimated expense",
|
||||||
|
"estimated_expenses.dialog.label": "New Estimated Expense",
|
||||||
|
"estimated_expenses.dialog.estimated_expense": "Estimated Expense Name",
|
||||||
|
"estimated_expenses.dialog.quantity": "Quantity",
|
||||||
|
"estimated_expenses.dialog.unit_price": "Unit Price",
|
||||||
|
"estimated_expenses.dialog.total": "Total",
|
||||||
|
"estimated_expenses.dialog.charge": "Charge",
|
||||||
|
"estimated_expenses.dialog.percentage": "Percentage",
|
||||||
|
"estimated_expenses.dialog.estimated_amount": "Estimated Amount:",
|
||||||
|
"estimated_expenses.dialog.cost_to_you": "Cost to you",
|
||||||
|
"estimated_expenses.dialog.what_you_ll_charge": "What you'll charge",
|
||||||
|
"estimated_expense.schema.label.estimated_expense": "Estimated expense name",
|
||||||
|
"estimated_expense.schema.label.quantity": "Quantity",
|
||||||
|
"estimated_expense.schema.label.unit_price": "Unit price",
|
||||||
|
"purchases.column.date": "Date",
|
||||||
|
"purchases.column.type": "Type",
|
||||||
|
"purchases.column.transaction_no": "Transaction No",
|
||||||
|
"purchases.column.due_date": "Due Date",
|
||||||
|
"purchases.column.balance": "Balance",
|
||||||
|
"purchases.column.total": "Total",
|
||||||
|
"purchases.column.status": "Status",
|
||||||
|
"purchases.action.delete": "Delete",
|
||||||
|
"sales.column.date": "Date",
|
||||||
|
"sales.column.type": "Type",
|
||||||
|
"sales.column.transaction_no": "Transaction No",
|
||||||
|
"sales.column.due_date": "Due Date",
|
||||||
|
"sales.column.balance": "Balance",
|
||||||
|
"sales.column.total": "Total",
|
||||||
|
"sales.column.status": "Status",
|
||||||
|
"sales.action.delete": "Delete"
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,12 @@ const initialState = {
|
|||||||
timesheets: {
|
timesheets: {
|
||||||
tableSize: 'medium',
|
tableSize: 'medium',
|
||||||
},
|
},
|
||||||
|
purchases: {
|
||||||
|
tableSize: 'medium',
|
||||||
|
},
|
||||||
|
sales: {
|
||||||
|
tableSize: 'medium',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user