mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
feat: add api project tasks.
This commit is contained in:
@@ -19,6 +19,7 @@ export const TABLES = {
|
||||
WAREHOUSE_TRANSFERS: 'warehouse_transfers',
|
||||
PROJECTS: 'projects',
|
||||
TIMESHEETS: 'timesheets',
|
||||
PROJECT_TASKS: 'project_tasks',
|
||||
};
|
||||
|
||||
export const TABLE_SIZE = {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
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 { useDeleteProjectTask } from '../../hooks';
|
||||
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Project tasks delete alert.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTaskDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { taskId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: deleteProjectTaskMutate, isLoading } =
|
||||
useDeleteProjectTask();
|
||||
|
||||
// handle cancel delete alert.
|
||||
const handleCancelDeleteAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// handleConfirm delete project
|
||||
const handleConfirmProjectDelete = () => {
|
||||
deleteProjectTaskMutate(taskId)
|
||||
.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,
|
||||
)(ProjectTaskDeleteAlert);
|
||||
@@ -1,8 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
const ProjectDeleteAlert = React.lazy(() => import('./ProjectDeleteAlert'));
|
||||
const ProjectTaskDeleteAlert = React.lazy(
|
||||
() => import('./ProjectTaskDeleteAlert'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Project alerts.
|
||||
*/
|
||||
export default [{ name: 'project-delete', component: ProjectDeleteAlert }];
|
||||
export default [
|
||||
{ name: 'project-delete', component: ProjectDeleteAlert },
|
||||
{ name: 'project-task-delete', component: ProjectTaskDeleteAlert },
|
||||
];
|
||||
|
||||
@@ -58,7 +58,8 @@ function ProjectDetailActionsBar({
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('timesheets', 'tableSize', size) &&
|
||||
addSetting('sales', 'tableSize', size) &&
|
||||
addSetting('purchases', 'tableSize', size);
|
||||
addSetting('purchases', 'tableSize', size) &&
|
||||
addSetting('project_tasks', 'tableSize', size);
|
||||
};
|
||||
|
||||
const handleTimeEntryBtnClick = () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import ProjectTimeSheets from './ProjectTimeSheets';
|
||||
import ProjectTasks from './ProjectTasks';
|
||||
import ProjectPurchasesTable from './ProjectPurchasesTable';
|
||||
import ProjectSalesTable from './ProjectSalesTable';
|
||||
|
||||
@@ -17,9 +18,14 @@ export default function ProjectDetailTabs() {
|
||||
animate={true}
|
||||
large={true}
|
||||
renderActiveTabPanelOnly={true}
|
||||
defaultSelectedTabId={'purchases'}
|
||||
defaultSelectedTabId={'tasks'}
|
||||
>
|
||||
<Tab id="overview" title={intl.get('project_details.label.overview')} />
|
||||
<Tab
|
||||
id="tasks"
|
||||
title={intl.get('project_details.label.tasks')}
|
||||
panel={<ProjectTasks />}
|
||||
/>
|
||||
<Tab
|
||||
id="timesheet"
|
||||
title={intl.get('project_details.label.timesheet')}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useProjectTasks } from '../../../hooks';
|
||||
|
||||
const ProjectTaskContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Project task data provider.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTaskProvider({ ...props }) {
|
||||
const { id } = useParams();
|
||||
const projectId = parseInt(id, 10);
|
||||
|
||||
const {
|
||||
data: { projectTasks },
|
||||
isFetching: isProjectTasksFetching,
|
||||
isLoading: isProjectTasksLoading,
|
||||
} = useProjectTasks(projectId, {
|
||||
enabled: !!projectId,
|
||||
});
|
||||
|
||||
// provider payload.
|
||||
const provider = {
|
||||
projectId,
|
||||
projectTasks,
|
||||
isProjectTasksFetching,
|
||||
isProjectTasksLoading,
|
||||
};
|
||||
|
||||
return <ProjectTaskContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useProjectTaskContext = () => React.useContext(ProjectTaskContext);
|
||||
|
||||
export { ProjectTaskProvider, useProjectTaskContext };
|
||||
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { FormatDate } from '@/components';
|
||||
import {
|
||||
DetailFinancialCard,
|
||||
DetailFinancialSection,
|
||||
FinancialProgressBar,
|
||||
FinancialCardText,
|
||||
} from '../components';
|
||||
import { calculateStatus } from '@/utils';
|
||||
|
||||
/**
|
||||
* Project Tasks header.
|
||||
* @returns
|
||||
*/
|
||||
export function ProjectTasksHeader() {
|
||||
return (
|
||||
<DetailFinancialSection>
|
||||
<DetailFinancialCard label={'Project estimate'} value={'3.14'} />
|
||||
<DetailFinancialCard label={'Invoiced'} value={'0.00'}>
|
||||
<FinancialCardText>0% of project estimate</FinancialCardText>
|
||||
<FinancialProgressBar intent={Intent.NONE} value={0} />
|
||||
</DetailFinancialCard>
|
||||
<DetailFinancialCard label={'Time & Expenses'} value={'0.00'}>
|
||||
<FinancialCardText>0% of project estimate</FinancialCardText>
|
||||
<FinancialProgressBar intent={Intent.NONE} value={0} />
|
||||
</DetailFinancialCard>
|
||||
|
||||
<DetailFinancialCard label={'To be invoiced'} value={'3.14'} />
|
||||
<DetailFinancialCard
|
||||
label={'Deadline'}
|
||||
value={<FormatDate value={'2022-06-08T22:00:00.000Z'} />}
|
||||
>
|
||||
<FinancialCardText>4 days to go</FinancialCardText>
|
||||
</DetailFinancialCard>
|
||||
</DetailFinancialSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
DataTable,
|
||||
TableSkeletonRows,
|
||||
TableSkeletonHeader,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
import { ActionsMenu } from './components';
|
||||
import { useProjectTaskColumns } from './hooks';
|
||||
import { useMemorizedColumnsWidths } from '@/hooks';
|
||||
import { useProjectTaskContext } from './ProjectTaskProvider';
|
||||
import withSettings from '@/containers/Settings/withSettings';
|
||||
import withAlertsActions from '@/containers/Alert/withAlertActions';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
|
||||
function ProjectTaskTableRoot({
|
||||
// #withSettings
|
||||
projectTasksTableSize,
|
||||
|
||||
// #withDialog
|
||||
openDialog,
|
||||
// #withAlertsActions
|
||||
openAlert,
|
||||
}) {
|
||||
const { projectTasks } = useProjectTaskContext();
|
||||
|
||||
// Retrieve project task table columns.
|
||||
const columns = useProjectTaskColumns();
|
||||
|
||||
// Handle delete task.
|
||||
const handleDeleteTask = ({ id }) => {
|
||||
openAlert('project-task-delete', { taskId: id });
|
||||
};
|
||||
|
||||
const handleEditTask = ({ id }) => {
|
||||
openDialog('project-task-form', {
|
||||
taskId: id,
|
||||
action: 'edit',
|
||||
});
|
||||
};
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.PROJECT_TASKS);
|
||||
|
||||
return (
|
||||
<ProjectTaksDataTable
|
||||
columns={columns}
|
||||
data={projectTasks}
|
||||
manualSortBy={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
hideTableHeader={true}
|
||||
ContextMenu={ActionsMenu}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
size={projectTasksTableSize}
|
||||
payload={{
|
||||
onDelete: handleDeleteTask,
|
||||
onEdit: handleEditTask,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const ProjectTasksTable = compose(
|
||||
withAlertsActions,
|
||||
withDialogActions,
|
||||
withSettings(({ projectTasksSettings }) => ({
|
||||
projectTasksTableSize: projectTasksSettings?.tableSize,
|
||||
})),
|
||||
)(ProjectTaskTableRoot);
|
||||
|
||||
const ProjectTaksDataTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead .tr .th {
|
||||
.resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
import { Icon } from '@/components';
|
||||
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
||||
import { safeCallback } from '@/utils';
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
export function ActionsMenu({
|
||||
payload: { onEdit, onDelete },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={intl.get('project_task.action.edit_task')}
|
||||
onClick={safeCallback(onEdit, original)}
|
||||
/>
|
||||
<MenuItem
|
||||
text={intl.get('project_task.action.delete_task')}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
export function TaskAccessor(row) {
|
||||
return (
|
||||
<TaskRoot>
|
||||
<TaskHeader>
|
||||
<TaskTitle>{row.name}</TaskTitle>
|
||||
</TaskHeader>
|
||||
<TaskContent>
|
||||
{row.charge_type === 'hourly_rate'
|
||||
? row.rate + ' / hour'
|
||||
: row.charge_type}
|
||||
<TaskDescription>{row.estimate_minutes} estimated</TaskDescription>
|
||||
</TaskContent>
|
||||
</TaskRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const TaskRoot = styled.div`
|
||||
margin-left: 12px;
|
||||
`;
|
||||
const TaskHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-flow: wrap;
|
||||
`;
|
||||
const TaskTitle = styled.span`
|
||||
font-weight: 500;
|
||||
/* margin-right: 12px; */
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
const TaskContent = styled.div`
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
opacity: 0.75;
|
||||
margin-bottom: 0.1rem;
|
||||
line-height: 1.2rem;
|
||||
`;
|
||||
const TaskDescription = styled.span`
|
||||
&::before {
|
||||
content: '•';
|
||||
margin: 0.3rem;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import { TaskAccessor } from './components';
|
||||
|
||||
/**
|
||||
* Retrieve project tasks list columns.
|
||||
*/
|
||||
export function useProjectTaskColumns() {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Header',
|
||||
accessor: TaskAccessor,
|
||||
width: 100,
|
||||
className: 'name',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ProjectTasksHeader } from './ProjectTasksHeader';
|
||||
import { ProjectTasksTable } from './ProjectTasksTable';
|
||||
import { ProjectTaskProvider } from './ProjectTaskProvider';
|
||||
|
||||
export default function ProjectTasks() {
|
||||
return (
|
||||
<ProjectTaskProvider>
|
||||
<ProjectTasksHeader />
|
||||
<ProjectTasksTableCard>
|
||||
<ProjectTasksTable />
|
||||
</ProjectTasksTableCard>
|
||||
</ProjectTaskProvider>
|
||||
);
|
||||
}
|
||||
|
||||
const ProjectTasksTableCard = styled.div`
|
||||
margin: 22px 32px;
|
||||
border: 1px solid #c8cad0;
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
`;
|
||||
@@ -1,4 +1,3 @@
|
||||
//@ts-nocheck
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
@@ -2,14 +2,19 @@ import * as Yup from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
const Schema = Yup.object().shape({
|
||||
taskName: Yup.string()
|
||||
.label(intl.get('task.schema.label.task_name'))
|
||||
name: Yup.string()
|
||||
.label(intl.get('project_task.schema.label.task_name'))
|
||||
.required(),
|
||||
taskHouse: Yup.string().label(intl.get('task.schema.label.task_house')),
|
||||
taskCharge: Yup.string()
|
||||
.label(intl.get('task.schema.label.charge'))
|
||||
charge_type: Yup.string()
|
||||
.label(intl.get('project_task.schema.label.charge_type'))
|
||||
.required(),
|
||||
taskamount: Yup.number().label(intl.get('task.schema.label.amount')),
|
||||
rate: Yup.number()
|
||||
.label(intl.get('project_task.schema.label.rate'))
|
||||
.required(),
|
||||
cost_estimate: Yup.number().required(),
|
||||
estimate_minutes: Yup.string().label(
|
||||
intl.get('project_task.schema.label.task_house'),
|
||||
),
|
||||
});
|
||||
|
||||
export const CreateProjectTaskFormSchema = Schema;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Formik } from 'formik';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { AppToaster } from '@/components';
|
||||
import { CreateProjectTaskFormSchema } from './ProjectTaskForm.schema';
|
||||
import { useProjectTaskFormContext } from './ProjectTaskFormProvider';
|
||||
import { AppToaster } from '@/components';
|
||||
import ProjectTaskFormContent from './ProjectTaskFormContent';
|
||||
import withDialogActions from '@/containers/Dialog/withDialogActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
import { compose, transformToForm } from '@/utils';
|
||||
|
||||
const defaultInitialValues = {
|
||||
taskName: '',
|
||||
taskHouse: '00:00',
|
||||
taskCharge: 'hourly_rate',
|
||||
taskamount: '',
|
||||
name: '',
|
||||
charge_type: 'fixed_price',
|
||||
estimate_minutes: '',
|
||||
cost_estimate: '',
|
||||
rate: '0.00',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,19 +27,39 @@ function ProjectTaskForm({
|
||||
closeDialog,
|
||||
}) {
|
||||
// task form dialog context.
|
||||
const { dialogName } = useProjectTaskFormContext();
|
||||
const {
|
||||
taskId,
|
||||
projectId,
|
||||
isNewMode,
|
||||
dialogName,
|
||||
projectTask,
|
||||
createProjectTaskMutate,
|
||||
editProjectTaskMutate,
|
||||
} = useProjectTaskFormContext();
|
||||
|
||||
// Initial form values
|
||||
const initialValues = {
|
||||
...defaultInitialValues,
|
||||
...transformToForm(projectTask, defaultInitialValues),
|
||||
};
|
||||
|
||||
// Handles the form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
const form = {};
|
||||
const form = { ...values };
|
||||
|
||||
// Handle request response success.
|
||||
const onSuccess = (response) => {};
|
||||
const onSuccess = (response) => {
|
||||
AppToaster.show({
|
||||
message: intl.get(
|
||||
isNewMode
|
||||
? 'project_task.dialog.success_message'
|
||||
: 'project_task.dialog.edit_success_message',
|
||||
),
|
||||
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
// Handle request response errors.
|
||||
const onError = ({
|
||||
@@ -44,8 +67,13 @@ function ProjectTaskForm({
|
||||
data: { errors },
|
||||
},
|
||||
}) => {
|
||||
setSubmitting(false);
|
||||
setSubmitting(false);
|
||||
};
|
||||
if (isNewMode) {
|
||||
createProjectTaskMutate([projectId, form]).then(onSuccess).catch(onError);
|
||||
} else {
|
||||
editProjectTaskMutate([taskId, form]).then(onSuccess).catch(onError);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,9 +9,14 @@ export default function ProjectTaskFormDialogContent({
|
||||
// #ownProps
|
||||
dialogName,
|
||||
task,
|
||||
project,
|
||||
}) {
|
||||
return (
|
||||
<ProjectTaskFormProvider taskId={task} dialogName={dialogName}>
|
||||
<ProjectTaskFormProvider
|
||||
taskId={task}
|
||||
projectId={project}
|
||||
dialogName={dialogName}
|
||||
>
|
||||
<ProjectTaskForm />
|
||||
</ProjectTaskFormProvider>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useFormikContext } from 'formik';
|
||||
import { Classes, ControlGroup } from '@blueprintjs/core';
|
||||
import {
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
Row,
|
||||
FormattedMessage as T,
|
||||
} from '@/components';
|
||||
import { EstimateAmount } from './utils';
|
||||
import { taskChargeOptions } from '../common/modalChargeOptions';
|
||||
import { ChangeTypesSelect } from '../../components';
|
||||
|
||||
@@ -23,69 +23,48 @@ function ProjectTaskFormFields() {
|
||||
return (
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
{/*------------ Task Name -----------*/}
|
||||
<FFormGroup label={<T id={'project_task.dialog.task_name'} />} name={'taskName'}>
|
||||
<FInputGroup name="taskName" />
|
||||
<FFormGroup
|
||||
label={<T id={'project_task.dialog.task_name'} />}
|
||||
name={'taskName'}
|
||||
>
|
||||
<FInputGroup name="name" />
|
||||
</FFormGroup>
|
||||
{/*------------ Estimated Hours -----------*/}
|
||||
<Row>
|
||||
<Col xs={4}>
|
||||
<FFormGroup
|
||||
label={<T id={'project_task.dialog.estimated_hours'} />}
|
||||
name={'taskHouse'}
|
||||
name={'estimate_minutes'}
|
||||
>
|
||||
<FInputGroup name="taskHouse" />
|
||||
<FInputGroup name="estimate_minutes" />
|
||||
</FFormGroup>
|
||||
</Col>
|
||||
{/*------------ Charge -----------*/}
|
||||
<Col xs={8}>
|
||||
<FFormGroup
|
||||
name={'taskCharge'}
|
||||
name={'charge_type'}
|
||||
className={'form-group--select-list'}
|
||||
label={<T id={'project_task.dialog.charge'} />}
|
||||
>
|
||||
<ControlGroup>
|
||||
<ChangeTypesSelect
|
||||
name="taskCharge"
|
||||
name="charge_type"
|
||||
items={taskChargeOptions}
|
||||
popoverProps={{ minimal: true }}
|
||||
filterable={false}
|
||||
/>
|
||||
<FInputGroup
|
||||
name="taskamount"
|
||||
disabled={values?.taskCharge === 'Non-chargeable'}
|
||||
name="rate"
|
||||
disabled={values?.charge_type === 'non_chargeable'}
|
||||
/>
|
||||
</ControlGroup>
|
||||
</FFormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
{/*------------ Estimated Amount -----------*/}
|
||||
<EstimatedAmountBase>
|
||||
<EstimatedAmountContent>
|
||||
<T id={'project_task.dialog.estimated_amount'} />
|
||||
<EstimateAmount>0.00</EstimateAmount>
|
||||
</EstimatedAmountContent>
|
||||
</EstimatedAmountBase>
|
||||
<EstimateAmount />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProjectTaskFormFields;
|
||||
|
||||
const EstimatedAmountBase = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
line-height: 1.5rem;
|
||||
opacity: 0.75;
|
||||
`;
|
||||
|
||||
const EstimatedAmountContent = styled.span`
|
||||
background-color: #fffdf5;
|
||||
padding: 0.1rem 0;
|
||||
`;
|
||||
|
||||
const EstimateAmount = styled.span`
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
useCreateProjectTask,
|
||||
useEditProjectTask,
|
||||
useProjectTask,
|
||||
} from '../../hooks';
|
||||
import { DialogContent } from '@/components';
|
||||
|
||||
const ProjectTaskFormContext = React.createContext();
|
||||
@@ -11,15 +16,34 @@ function ProjectTaskFormProvider({
|
||||
// #ownProps
|
||||
dialogName,
|
||||
taskId,
|
||||
projectId,
|
||||
...props
|
||||
}) {
|
||||
// Create and edit project task mutations.
|
||||
const { mutateAsync: createProjectTaskMutate } = useCreateProjectTask();
|
||||
const { mutateAsync: editProjectTaskMutate } = useEditProjectTask();
|
||||
|
||||
// Handle fetch project task detail.
|
||||
const { data: projectTask, isLoading: isProjectTaskLoading } = useProjectTask(
|
||||
taskId,
|
||||
{
|
||||
enabled: !!taskId,
|
||||
},
|
||||
);
|
||||
|
||||
const isNewMode = !taskId;
|
||||
// State provider.
|
||||
const provider = {
|
||||
dialogName,
|
||||
isNewMode,
|
||||
projectId,
|
||||
projectTask,
|
||||
createProjectTaskMutate,
|
||||
editProjectTaskMutate,
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent isLoading={isProjectTaskLoading}>
|
||||
<ProjectTaskFormContext.Provider value={provider} {...props} />
|
||||
</DialogContent>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components';
|
||||
import withDialogRedux from '@/components/DialogReduxConnect';
|
||||
import { compose } from '@/utils';
|
||||
@@ -15,20 +15,30 @@ const ProjectTaskFormDialogContent = React.lazy(
|
||||
*/
|
||||
function ProjectTaskFormDialog({
|
||||
dialogName,
|
||||
payload: { taskId = null },
|
||||
payload: { taskId = null, projectId = null, action },
|
||||
isOpen,
|
||||
}) {
|
||||
return (
|
||||
<Dialog
|
||||
name={dialogName}
|
||||
title={intl.get('project_task.dialog.new_task')}
|
||||
title={
|
||||
action === 'edit' ? (
|
||||
<T id="project_task.dialog.edit_task" />
|
||||
) : (
|
||||
<T id={'project_task.dialog.new_task'} />
|
||||
)
|
||||
}
|
||||
isOpen={isOpen}
|
||||
autoFocus={true}
|
||||
canEscapeKeyClose={true}
|
||||
style={{ width: '500px' }}
|
||||
>
|
||||
<DialogSuspense>
|
||||
<ProjectTaskFormDialogContent dialogName={dialogName} task={taskId} />
|
||||
<ProjectTaskFormDialogContent
|
||||
dialogName={dialogName}
|
||||
task={taskId}
|
||||
project={projectId}
|
||||
/>
|
||||
</DialogSuspense>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { useFormikContext } from 'formik';
|
||||
import styled from 'styled-components';
|
||||
import { Choose, FormattedMessage as T } from '@/components';
|
||||
|
||||
export function EstimateAmount() {
|
||||
const { values } = useFormikContext();
|
||||
|
||||
// Calculate estimate amount.
|
||||
const estimatedAmount = _.multiply(values.rate, values.estimate_minutes);
|
||||
|
||||
return (
|
||||
<EstimatedAmountBase>
|
||||
<EstimatedAmountContent>
|
||||
<Choose>
|
||||
<Choose.When condition={values?.charge_type === 'hourly_rate'}>
|
||||
<T id={'project_task.dialog.estimated_amount'} />
|
||||
<EstimatedAmount>{estimatedAmount}</EstimatedAmount>
|
||||
</Choose.When>
|
||||
<Choose.When condition={values?.charge_type === 'fixed_price'}>
|
||||
<T id={'project_task.dialog.total'} />
|
||||
<EstimatedAmount>{values.rate}</EstimatedAmount>
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<T id={'project_task.dialog.total'} />
|
||||
<EstimatedAmount>0.00</EstimatedAmount>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</EstimatedAmountContent>
|
||||
</EstimatedAmountBase>
|
||||
);
|
||||
}
|
||||
|
||||
const EstimatedAmountBase = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
font-size: 14px;
|
||||
line-height: 1.5rem;
|
||||
opacity: 0.75;
|
||||
`;
|
||||
|
||||
const EstimatedAmountContent = styled.span`
|
||||
background-color: #fffdf5;
|
||||
padding: 0.1rem 0;
|
||||
`;
|
||||
|
||||
const EstimatedAmount = styled.span`
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
margin-left: 10px;
|
||||
`;
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as Yup from 'yup';
|
||||
import intl from 'react-intl-universal';
|
||||
import { DATATYPES_LENGTH } from '@/constants/dataTypes';
|
||||
|
||||
const Schema = Yup.object().shape({
|
||||
date: Yup.date()
|
||||
|
||||
@@ -10,14 +10,9 @@ export default function ProjectTimeEntryFormDialogContent({
|
||||
// #ownProps
|
||||
dialogName,
|
||||
project,
|
||||
timeEntry,
|
||||
}) {
|
||||
return (
|
||||
<ProjectTimeEntryFormProvider
|
||||
projectId={project}
|
||||
timeEntryId={timeEntry}
|
||||
dialogName={dialogName}
|
||||
>
|
||||
<ProjectTimeEntryFormProvider projectId={project} dialogName={dialogName}>
|
||||
<ProjectTimeEntryForm />
|
||||
</ProjectTimeEntryFormProvider>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,6 @@ function ProjectTimeEntryFormProvider({
|
||||
// #ownProps
|
||||
dialogName,
|
||||
projectId,
|
||||
timeEntryId,
|
||||
...props
|
||||
}) {
|
||||
const provider = {
|
||||
|
||||
@@ -15,7 +15,7 @@ const ProjectTimeEntryFormDialogContent = React.lazy(
|
||||
function ProjectTimeEntryFormDialog({
|
||||
dialogName,
|
||||
isOpen,
|
||||
payload: { projectId = null, timeEntryId = null },
|
||||
payload: { projectId },
|
||||
}) {
|
||||
return (
|
||||
<ProjectTimeEntryFormDialogRoot
|
||||
@@ -30,7 +30,6 @@ function ProjectTimeEntryFormDialog({
|
||||
<ProjectTimeEntryFormDialogContent
|
||||
dialogName={dialogName}
|
||||
project={projectId}
|
||||
timeEntry={timeEntryId}
|
||||
/>
|
||||
</DialogSuspense>
|
||||
</ProjectTimeEntryFormDialogRoot>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { DataTable,TableSkeletonRows ,TableSkeletonHeader } from '@/components';
|
||||
import {
|
||||
DataTable,
|
||||
TableSkeletonRows,
|
||||
TableSkeletonHeader,
|
||||
} from '@/components';
|
||||
import { TABLES } from '@/constants/tables';
|
||||
import ProjectsEmptyStatus from './ProjectsEmptyStatus';
|
||||
import { useProjectsListContext } from './ProjectsListProvider';
|
||||
@@ -59,8 +63,10 @@ function ProjectsDataTable({
|
||||
};
|
||||
|
||||
// Handle new task button click.
|
||||
const handleNewTaskButtonClick = () => {
|
||||
openDialog('project-task-form');
|
||||
const handleNewTaskButtonClick = (project) => {
|
||||
openDialog('project-task-form', {
|
||||
projectId: project.id,
|
||||
});
|
||||
};
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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' },
|
||||
{ name: intl.get('project_task.dialog.hourly_rate'), value: 'hourly_rate' },
|
||||
{ name: intl.get('project_task.dialog.fixed_price'), value: 'fixed_price' },
|
||||
{ name: intl.get('project_task.dialog.non_chargeable'), value: 'non_chargeable' },
|
||||
];
|
||||
|
||||
export const expenseChargeOption = [
|
||||
|
||||
@@ -1,124 +1,2 @@
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
||||
export * from './projects'
|
||||
export * from './projectsTask'
|
||||
124
src/containers/Projects/hooks/projects.ts
Normal file
124
src/containers/Projects/hooks/projects.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
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);
|
||||
},
|
||||
};
|
||||
}
|
||||
119
src/containers/Projects/hooks/projectsTask.tsx
Normal file
119
src/containers/Projects/hooks/projectsTask.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import { useQueryClient, useMutation } from 'react-query';
|
||||
import { useRequestQuery } from '@/hooks/useQueryRequest';
|
||||
import useApiRequest from '@/hooks/useRequest';
|
||||
import t from './type';
|
||||
|
||||
// Common invalidate queries.
|
||||
const commonInvalidateQueries = (queryClient) => {
|
||||
// Invalidate projects.
|
||||
queryClient.invalidateQueries(t.PROJECTS);
|
||||
// Invalidate project tasks.
|
||||
queryClient.invalidateQueries(t.PROJECT_TASKS);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new project task.
|
||||
* @param props
|
||||
*/
|
||||
export function useCreateProjectTask(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.post(`/projects/${id}/tasks`, values),
|
||||
{
|
||||
onSuccess: (res, [id, values]) => {
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given project task.
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function useEditProjectTask(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.post(`tasks/${id}`, values),
|
||||
{
|
||||
onSuccess: (res, [id, values]) => {
|
||||
// Invalidate specific project task.
|
||||
queryClient.invalidateQueries([t.PROJECT_TASK, id]);
|
||||
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given project task.
|
||||
* @param props
|
||||
*/
|
||||
export function useDeleteProjectTask(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.delete(`tasks/${id}`), {
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate specific project task.
|
||||
queryClient.invalidateQueries([t.PROJECT_TASK, id]);
|
||||
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive the given project task.
|
||||
* @param taskId
|
||||
* @param props
|
||||
* @param requestProps
|
||||
* @returns
|
||||
*/
|
||||
export function useProjectTask(taskId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.PROJECT, taskId],
|
||||
{ method: 'get', url: `tasks/${taskId}`, ...requestProps },
|
||||
{
|
||||
select: (res) => res.data.task,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const transformProjectTasks = (res) => ({
|
||||
projectTasks: res.data.tasks,
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param projectId - Project id.
|
||||
* @param query
|
||||
* @param requestProps
|
||||
* @returns
|
||||
*/
|
||||
export function useProjectTasks(projectId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.PROJECT_TASKS, projectId],
|
||||
{ method: 'get', url: `projects/${projectId}/tasks`, ...requestProps },
|
||||
{
|
||||
select: transformProjectTasks,
|
||||
defaultData: {
|
||||
projectTasks: [],
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -8,4 +8,9 @@ const PROJECTS = {
|
||||
PROJECTS: 'PROJECTS',
|
||||
};
|
||||
|
||||
export default { ...PROJECTS, ...CUSTOMERS };
|
||||
const PROJECT_TASKS ={
|
||||
PROJECT_TASKS:'PROJECT_TASKS',
|
||||
PROJECT_TASK:'PROJECT_TASK',
|
||||
}
|
||||
|
||||
export default { ...PROJECTS, ...CUSTOMERS,...PROJECT_TASKS };
|
||||
|
||||
@@ -23,6 +23,7 @@ export default (mapState) => {
|
||||
vendorsCreditNoteSetting: state.settings.data.vendorCredit,
|
||||
warehouseTransferSettings: state.settings.data.warehouseTransfers,
|
||||
projectSettings:state.settings.data.projects,
|
||||
projectTasksSettings:state.settings.data.projectTasks,
|
||||
timesheetsSettings:state.settings.data.timesheets
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
|
||||
@@ -2067,13 +2067,19 @@
|
||||
"projects.empty_status.description":"",
|
||||
"projects.empty_status.action":"New Project",
|
||||
"project_task.dialog.new_task": "New Task",
|
||||
"project_task.dialog.edit_task": "Edit Task",
|
||||
"project_task.dialog.task_name": "Task Name",
|
||||
"project_task.dialog.estimated_hours": "Estimate Hours",
|
||||
"project_task.dialog.charge": "Charge",
|
||||
"project_task.dialog.total": "Total",
|
||||
"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_task.dialog.success_message":"The task has been created successfully.",
|
||||
"project_task.dialog.edit_success_message":"The task has been created successfully.",
|
||||
"project_task.action.edit_task": "Edit Task",
|
||||
"project_task.action.delete_task": "Delete Task",
|
||||
"project.schema.label.contact": "Contact",
|
||||
"project.schema.label.project_name": "Project name",
|
||||
"project.schema.label.deadline": "Deadline",
|
||||
@@ -2081,12 +2087,14 @@
|
||||
"project.schema.label.project_cost": "Project cost",
|
||||
"project_task.schema.label.task_name": "Task name",
|
||||
"project_task.schema.label.task_house": "Task house",
|
||||
"project_task.schema.label.charge": "Charge",
|
||||
"project_task.schema.label.charge_type": "Charge type",
|
||||
"project_task.schema.label.rate": "Rate",
|
||||
"project_task.schema.label.amount": "Amount",
|
||||
"projcet_details.action.new_transaction": "New Transaction",
|
||||
"projcet_details.action.time_entry": "Time",
|
||||
"projcet_details.action.edit_project": "Edit Project",
|
||||
"project_details.label.overview": "Overview",
|
||||
"project_details.label.tasks": "Tasks",
|
||||
"project_details.label.timesheet": "Timesheet",
|
||||
"project_details.label.purchases": "Purchases",
|
||||
"project_details.label.sales": "Sales",
|
||||
|
||||
@@ -61,6 +61,9 @@ const initialState = {
|
||||
warehouseTransfer: {
|
||||
tableSize: 'medium',
|
||||
},
|
||||
projectTasks: {
|
||||
tableSize: 'medium',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user