mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +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'),
|
||||
);
|
||||
|
||||
const ProjectStatusAlert = React.lazy(() => import('./ProjectStatusAlert'));
|
||||
|
||||
/**
|
||||
* Project alerts.
|
||||
*/
|
||||
@@ -15,4 +17,5 @@ export default [
|
||||
{ name: 'project-delete', component: ProjectDeleteAlert },
|
||||
{ name: 'project-task-delete', component: ProjectTaskDeleteAlert },
|
||||
{ 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={'Deadline'}
|
||||
value={<FormatDate value={'2022-06-08T22:00:00.000Z'} />}
|
||||
value={<FormatDate value={project.deadline_formatted} />}
|
||||
>
|
||||
<FinancialCardText>4 days to go</FinancialCardText>
|
||||
</DetailFinancialCard>
|
||||
|
||||
@@ -1,9 +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_expense'), path: 'expense' },
|
||||
{
|
||||
name: intl.get('project_details.new_estimated_expenses'),
|
||||
name: intl.get('project_details.new_estimated_expense'),
|
||||
path: 'estimated_expense',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -43,6 +43,10 @@ function ProjectsDataTable({
|
||||
openAlert('project-delete', { projectId: id });
|
||||
};
|
||||
|
||||
const handleProjectStatus = ({ id, status_formatted }) => {
|
||||
openAlert('project-status', { projectId: id, status: status_formatted });
|
||||
};
|
||||
|
||||
// Retrieve projects table columns.
|
||||
const columns = useProjectsListColumns();
|
||||
|
||||
@@ -109,6 +113,7 @@ function ProjectsDataTable({
|
||||
onEdit: handleEditProject,
|
||||
onDelete: handleDeleteProject,
|
||||
onNewTask: handleNewTaskButtonClick,
|
||||
onStatus: handleProjectStatus,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -10,21 +10,27 @@ import {
|
||||
Intent,
|
||||
ProgressBar,
|
||||
} 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';
|
||||
|
||||
/**
|
||||
* project status.
|
||||
*/
|
||||
export function ProjectStatus({ project }) {
|
||||
export function ProjectStatus({ row }) {
|
||||
return (
|
||||
<ProjectStatusRoot>
|
||||
<ProjectStatusTaskAmount>{project.task_amount}</ProjectStatusTaskAmount>
|
||||
<ProjectStatusTaskAmount>{row.cost_estimate}</ProjectStatusTaskAmount>
|
||||
<ProjectProgressBar
|
||||
animate={false}
|
||||
stripes={false}
|
||||
// intent={Intent.PRIMARY}
|
||||
value={calculateStatus(project.task_amount, project.cost_estimate)}
|
||||
intent={Intent.PRIMARY}
|
||||
value={calculateStatus(100, row.cost_estimate)}
|
||||
/>
|
||||
</ProjectStatusRoot>
|
||||
);
|
||||
@@ -33,22 +39,22 @@ export function ProjectStatus({ project }) {
|
||||
/**
|
||||
* status accessor.
|
||||
*/
|
||||
export const StatusAccessor = (project) => {
|
||||
export const StatusAccessor = (row) => {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={project.is_process}>
|
||||
<ProjectStatus project={project} />
|
||||
<Choose.When condition={row.status_formatted === 'InProgress'}>
|
||||
<ProjectStatus row={row} />
|
||||
</Choose.When>
|
||||
<Choose.When condition={project.is_closed}>
|
||||
<Choose.When condition={row.status_formatted === 'Closed'}>
|
||||
<StatusTag minimal={true} intent={Intent.SUCCESS} round={true}>
|
||||
<T id={'closed'} />
|
||||
{row.status_formatted}
|
||||
</StatusTag>
|
||||
</Choose.When>
|
||||
<Choose.When condition={project.is_draft}>
|
||||
<StatusTag round={true} minimal={true}>
|
||||
<Choose.Otherwise>
|
||||
<StatusTag minimal={true} round={true}>
|
||||
<T id={'draft'} />
|
||||
</StatusTag>
|
||||
</Choose.When>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
};
|
||||
@@ -67,7 +73,7 @@ export const AvatarCell = ({ row: { original }, size }) => (
|
||||
*/
|
||||
export const ActionsMenu = ({
|
||||
row: { original },
|
||||
payload: { onEdit, onDelete, onViewDetails, onNewTask },
|
||||
payload: { onEdit, onDelete, onViewDetails, onNewTask, onStatus },
|
||||
}) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
@@ -86,6 +92,19 @@ export const ActionsMenu = ({
|
||||
text={intl.get('projects.action.new_task')}
|
||||
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 />
|
||||
<MenuItem
|
||||
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();
|
||||
|
||||
return {
|
||||
|
||||
@@ -66,7 +66,7 @@ export default function useApiRequest() {
|
||||
if (lockedError) {
|
||||
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 });
|
||||
setLogout();
|
||||
}
|
||||
@@ -97,6 +97,10 @@ export default function useApiRequest() {
|
||||
return http.put(`/api/${resource}`, params);
|
||||
},
|
||||
|
||||
patch(resource, params, config) {
|
||||
return http.patch(`/api/${resource}`, params, config);
|
||||
},
|
||||
|
||||
delete(resource, params) {
|
||||
return http.delete(`/api/${resource}`, params);
|
||||
},
|
||||
|
||||
@@ -2064,6 +2064,8 @@
|
||||
"projects.dialog.edit_project": "Edit Project",
|
||||
"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.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.description": "",
|
||||
"projects.empty_status.action": "New Project",
|
||||
@@ -2078,7 +2080,7 @@
|
||||
"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.dialog.edit_success_message": "The task has been edited successfully.",
|
||||
"project_task.action.edit_task": "Edit Task",
|
||||
"project_task.action.delete_task": "Delete Task",
|
||||
"project_task.rate": "{rate} /hour",
|
||||
@@ -2106,15 +2108,17 @@
|
||||
"project_details.label.purchases": "Purchases",
|
||||
"project_details.label.sales": "Sales",
|
||||
"project_details.label.journals": "Journals",
|
||||
"project_details.new_expenses": "New Expenses",
|
||||
"project_details.new_estimated_expenses": "New Estimated Expenses",
|
||||
"timesheets.actions.delete_timesheet": "Delete",
|
||||
"project_details.new_expense": "New Expense",
|
||||
"project_details.new_estimated_expense": "New Estimated Expense",
|
||||
"timesheets.action.delete_timesheet": "Delete",
|
||||
"timesheets.action.edit_timesheet": "Edit Timesheet",
|
||||
"timesheets.column.date": "Date",
|
||||
"timesheets.column.task": "Task",
|
||||
"timesheets.column.user": "User",
|
||||
"timesheets.column.time": "Time",
|
||||
"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.task": "Task",
|
||||
"project_time_entry.dialog.description": "Description",
|
||||
|
||||
Reference in New Issue
Block a user