mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-22 07:40:32 +00:00
fix: add projects status
This commit is contained in:
@@ -0,0 +1,75 @@
|
|||||||
|
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 { useProjectStatus } from '../../hooks';
|
||||||
|
|
||||||
|
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||||
|
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||||
|
|
||||||
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project status alert.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
function ProjectStatusAlert({
|
||||||
|
name,
|
||||||
|
isOpen,
|
||||||
|
payload: { projectId, status },
|
||||||
|
|
||||||
|
// #withAlertActions
|
||||||
|
closeAlert,
|
||||||
|
}) {
|
||||||
|
const { mutateAsync: statusProjectMutate, isLoading } = useProjectStatus();
|
||||||
|
|
||||||
|
// handle cancel alert.
|
||||||
|
const handleCancelAlert = () => {
|
||||||
|
closeAlert(name);
|
||||||
|
};
|
||||||
|
|
||||||
|
// handle confirm alert.
|
||||||
|
const handleConfirmAlert = () => {
|
||||||
|
const values = {
|
||||||
|
status: status !== 'InProgress' ? 'InProgress' : 'Closed',
|
||||||
|
};
|
||||||
|
|
||||||
|
statusProjectMutate([projectId, values])
|
||||||
|
.then(() => {
|
||||||
|
AppToaster.show({
|
||||||
|
message: intl.get('projects.alert.status_message'),
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(
|
||||||
|
({
|
||||||
|
response: {
|
||||||
|
data: { errors },
|
||||||
|
},
|
||||||
|
}) => {},
|
||||||
|
)
|
||||||
|
.finally(() => {
|
||||||
|
closeAlert(name);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert
|
||||||
|
cancelButtonText={<T id={'cancel'} />}
|
||||||
|
confirmButtonText={<T id={'save'} />}
|
||||||
|
intent={Intent.WARNING}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onCancel={handleCancelAlert}
|
||||||
|
onConfirm={handleConfirmAlert}
|
||||||
|
loading={isLoading}
|
||||||
|
>
|
||||||
|
<FormattedHTMLMessage id="projects.alert.are_you_sure_you_want" />
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withAlertStoreConnect(),
|
||||||
|
withAlertActions,
|
||||||
|
)(ProjectStatusAlert);
|
||||||
@@ -8,6 +8,8 @@ const ProjectTimesheetDeleteAlert = React.lazy(
|
|||||||
() => import('./ProjectTimesheetDeleteAlert'),
|
() => import('./ProjectTimesheetDeleteAlert'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ProjectStatusAlert = React.lazy(() => import('./ProjectStatusAlert'));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Project alerts.
|
* Project alerts.
|
||||||
*/
|
*/
|
||||||
@@ -15,4 +17,5 @@ export default [
|
|||||||
{ name: 'project-delete', component: ProjectDeleteAlert },
|
{ name: 'project-delete', component: ProjectDeleteAlert },
|
||||||
{ name: 'project-task-delete', component: ProjectTaskDeleteAlert },
|
{ name: 'project-task-delete', component: ProjectTaskDeleteAlert },
|
||||||
{ name: 'project-timesheet-delete', component: ProjectTimesheetDeleteAlert },
|
{ name: 'project-timesheet-delete', component: ProjectTimesheetDeleteAlert },
|
||||||
|
{ name: 'project-status', component: ProjectStatusAlert },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export function ProjectTasksHeader() {
|
|||||||
<DetailFinancialCard label={'To be invoiced'} value={'3.14'} />
|
<DetailFinancialCard label={'To be invoiced'} value={'3.14'} />
|
||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
label={'Deadline'}
|
label={'Deadline'}
|
||||||
value={<FormatDate value={'2022-06-08T22:00:00.000Z'} />}
|
value={<FormatDate value={project.deadline_formatted} />}
|
||||||
>
|
>
|
||||||
<FinancialCardText>4 days to go</FinancialCardText>
|
<FinancialCardText>4 days to go</FinancialCardText>
|
||||||
</DetailFinancialCard>
|
</DetailFinancialCard>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
|
|
||||||
export const projectTranslations = [
|
export const projectTranslations = [
|
||||||
{ name: intl.get('project_details.new_expenses'), path: 'expense' },
|
{ name: intl.get('project_details.new_expense'), path: 'expense' },
|
||||||
{
|
{
|
||||||
name: intl.get('project_details.new_estimated_expenses'),
|
name: intl.get('project_details.new_estimated_expense'),
|
||||||
path: 'estimated_expense',
|
path: 'estimated_expense',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ function ProjectsDataTable({
|
|||||||
openAlert('project-delete', { projectId: id });
|
openAlert('project-delete', { projectId: id });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleProjectStatus = ({ id, status_formatted }) => {
|
||||||
|
openAlert('project-status', { projectId: id, status: status_formatted });
|
||||||
|
};
|
||||||
|
|
||||||
// Retrieve projects table columns.
|
// Retrieve projects table columns.
|
||||||
const columns = useProjectsListColumns();
|
const columns = useProjectsListColumns();
|
||||||
|
|
||||||
@@ -109,6 +113,7 @@ function ProjectsDataTable({
|
|||||||
onEdit: handleEditProject,
|
onEdit: handleEditProject,
|
||||||
onDelete: handleDeleteProject,
|
onDelete: handleDeleteProject,
|
||||||
onNewTask: handleNewTaskButtonClick,
|
onNewTask: handleNewTaskButtonClick,
|
||||||
|
onStatus: handleProjectStatus,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -10,21 +10,27 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
ProgressBar,
|
ProgressBar,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Icon, FormatDate, Choose, FormattedMessage as T } from '@/components';
|
import {
|
||||||
|
Icon,
|
||||||
|
FormatDate,
|
||||||
|
Choose,
|
||||||
|
If,
|
||||||
|
FormattedMessage as T,
|
||||||
|
} from '@/components';
|
||||||
import { safeCallback, firstLettersArgs, calculateStatus } from '@/utils';
|
import { safeCallback, firstLettersArgs, calculateStatus } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* project status.
|
* project status.
|
||||||
*/
|
*/
|
||||||
export function ProjectStatus({ project }) {
|
export function ProjectStatus({ row }) {
|
||||||
return (
|
return (
|
||||||
<ProjectStatusRoot>
|
<ProjectStatusRoot>
|
||||||
<ProjectStatusTaskAmount>{project.task_amount}</ProjectStatusTaskAmount>
|
<ProjectStatusTaskAmount>{row.cost_estimate}</ProjectStatusTaskAmount>
|
||||||
<ProjectProgressBar
|
<ProjectProgressBar
|
||||||
animate={false}
|
animate={false}
|
||||||
stripes={false}
|
stripes={false}
|
||||||
// intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
value={calculateStatus(project.task_amount, project.cost_estimate)}
|
value={calculateStatus(100, row.cost_estimate)}
|
||||||
/>
|
/>
|
||||||
</ProjectStatusRoot>
|
</ProjectStatusRoot>
|
||||||
);
|
);
|
||||||
@@ -33,22 +39,22 @@ export function ProjectStatus({ project }) {
|
|||||||
/**
|
/**
|
||||||
* status accessor.
|
* status accessor.
|
||||||
*/
|
*/
|
||||||
export const StatusAccessor = (project) => {
|
export const StatusAccessor = (row) => {
|
||||||
return (
|
return (
|
||||||
<Choose>
|
<Choose>
|
||||||
<Choose.When condition={project.is_process}>
|
<Choose.When condition={row.status_formatted === 'InProgress'}>
|
||||||
<ProjectStatus project={project} />
|
<ProjectStatus row={row} />
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={project.is_closed}>
|
<Choose.When condition={row.status_formatted === 'Closed'}>
|
||||||
<StatusTag minimal={true} intent={Intent.SUCCESS} round={true}>
|
<StatusTag minimal={true} intent={Intent.SUCCESS} round={true}>
|
||||||
<T id={'closed'} />
|
{row.status_formatted}
|
||||||
</StatusTag>
|
</StatusTag>
|
||||||
</Choose.When>
|
</Choose.When>
|
||||||
<Choose.When condition={project.is_draft}>
|
<Choose.Otherwise>
|
||||||
<StatusTag round={true} minimal={true}>
|
<StatusTag minimal={true} round={true}>
|
||||||
<T id={'draft'} />
|
<T id={'draft'} />
|
||||||
</StatusTag>
|
</StatusTag>
|
||||||
</Choose.When>
|
</Choose.Otherwise>
|
||||||
</Choose>
|
</Choose>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -67,7 +73,7 @@ export const AvatarCell = ({ row: { original }, size }) => (
|
|||||||
*/
|
*/
|
||||||
export const ActionsMenu = ({
|
export const ActionsMenu = ({
|
||||||
row: { original },
|
row: { original },
|
||||||
payload: { onEdit, onDelete, onViewDetails, onNewTask },
|
payload: { onEdit, onDelete, onViewDetails, onNewTask, onStatus },
|
||||||
}) => (
|
}) => (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
@@ -86,6 +92,19 @@ export const ActionsMenu = ({
|
|||||||
text={intl.get('projects.action.new_task')}
|
text={intl.get('projects.action.new_task')}
|
||||||
onClick={safeCallback(onNewTask, original)}
|
onClick={safeCallback(onNewTask, original)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<MenuItem text={'Status'} icon={<Icon icon="plus" />}>
|
||||||
|
<If condition={original.status !== 'InProgress'}>
|
||||||
|
<MenuItem
|
||||||
|
text={'InProgress'}
|
||||||
|
onClick={safeCallback(onStatus, original)}
|
||||||
|
/>
|
||||||
|
</If>
|
||||||
|
<If condition={original.status !== 'Closed'}>
|
||||||
|
<MenuItem text={'Closed'} onClick={safeCallback(onStatus, original)} />
|
||||||
|
</If>
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<MenuItem
|
<MenuItem
|
||||||
text={intl.get('projects.action.delete_project')}
|
text={intl.get('projects.action.delete_project')}
|
||||||
|
|||||||
@@ -113,7 +113,30 @@ export function useProjects(query, props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRefreshInvoices() {
|
/**
|
||||||
|
*
|
||||||
|
* @param props
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function useProjectStatus(props) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const apiRequest = useApiRequest();
|
||||||
|
|
||||||
|
return useMutation(
|
||||||
|
([id, values]) => apiRequest.patch(`projects/${id}/status`, values),
|
||||||
|
{
|
||||||
|
onSuccess: (res, [id, values]) => {
|
||||||
|
// Invalidate specific project.
|
||||||
|
queryClient.invalidateQueries([t.PROJECT, id]);
|
||||||
|
|
||||||
|
commonInvalidateQueries(queryClient);
|
||||||
|
},
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRefreshProjects() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ export default function useApiRequest() {
|
|||||||
if (lockedError) {
|
if (lockedError) {
|
||||||
setGlobalErrors({ transactionsLocked: { ...lockedError.data } });
|
setGlobalErrors({ transactionsLocked: { ...lockedError.data } });
|
||||||
}
|
}
|
||||||
if (data.errors.find(e => e.type === 'USER_INACTIVE')) {
|
if (data.errors.find((e) => e.type === 'USER_INACTIVE')) {
|
||||||
setGlobalErrors({ userInactive: true });
|
setGlobalErrors({ userInactive: true });
|
||||||
setLogout();
|
setLogout();
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,10 @@ export default function useApiRequest() {
|
|||||||
return http.put(`/api/${resource}`, params);
|
return http.put(`/api/${resource}`, params);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
patch(resource, params, config) {
|
||||||
|
return http.patch(`/api/${resource}`, params, config);
|
||||||
|
},
|
||||||
|
|
||||||
delete(resource, params) {
|
delete(resource, params) {
|
||||||
return http.delete(`/api/${resource}`, params);
|
return http.delete(`/api/${resource}`, params);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2064,6 +2064,8 @@
|
|||||||
"projects.dialog.edit_project": "Edit Project",
|
"projects.dialog.edit_project": "Edit Project",
|
||||||
"projects.alert.delete_message": "The deleted project has been deleted successfully.",
|
"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.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.alert.status_message": "The project has been edited successfully.",
|
||||||
|
"projects.alert.are_you_sure_you_want":"Are you sure you want to edit this project?",
|
||||||
"projects.empty_status.title": "",
|
"projects.empty_status.title": "",
|
||||||
"projects.empty_status.description": "",
|
"projects.empty_status.description": "",
|
||||||
"projects.empty_status.action": "New Project",
|
"projects.empty_status.action": "New Project",
|
||||||
@@ -2078,7 +2080,7 @@
|
|||||||
"project_task.dialog.fixed_price": "Fixed price",
|
"project_task.dialog.fixed_price": "Fixed price",
|
||||||
"project_task.dialog.non_chargeable": "Non-chargeable",
|
"project_task.dialog.non_chargeable": "Non-chargeable",
|
||||||
"project_task.dialog.success_message": "The task has been created successfully.",
|
"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.dialog.edit_success_message": "The task has been edited successfully.",
|
||||||
"project_task.action.edit_task": "Edit Task",
|
"project_task.action.edit_task": "Edit Task",
|
||||||
"project_task.action.delete_task": "Delete Task",
|
"project_task.action.delete_task": "Delete Task",
|
||||||
"project_task.rate": "{rate} /hour",
|
"project_task.rate": "{rate} /hour",
|
||||||
@@ -2106,15 +2108,17 @@
|
|||||||
"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_expense": "New Expense",
|
||||||
"project_details.new_estimated_expenses": "New Estimated Expenses",
|
"project_details.new_estimated_expense": "New Estimated Expense",
|
||||||
"timesheets.actions.delete_timesheet": "Delete",
|
"timesheets.action.delete_timesheet": "Delete",
|
||||||
|
"timesheets.action.edit_timesheet": "Edit Timesheet",
|
||||||
"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",
|
||||||
"project_time_entry.dialog.label": "New Time Entry",
|
"project_time_entry.dialog.new_time_entry": "New Time Entry",
|
||||||
|
"project_time_entry.dialog.edit_time_entry": "Edit Time Entry",
|
||||||
"project_time_entry.dialog.project": "Project",
|
"project_time_entry.dialog.project": "Project",
|
||||||
"project_time_entry.dialog.task": "Task",
|
"project_time_entry.dialog.task": "Task",
|
||||||
"project_time_entry.dialog.description": "Description",
|
"project_time_entry.dialog.description": "Description",
|
||||||
|
|||||||
Reference in New Issue
Block a user