mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-18 13:50:31 +00:00
feat: add api project timesheet.
This commit is contained in:
@@ -32,8 +32,8 @@ function ProjectTaskDeleteAlert({
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// handleConfirm delete project
|
||||
const handleConfirmProjectDelete = () => {
|
||||
// handleConfirm delete project task
|
||||
const handleConfirmProjectTaskDelete = () => {
|
||||
deleteProjectTaskMutate(taskId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
@@ -61,7 +61,7 @@ function ProjectTaskDeleteAlert({
|
||||
intent={Intent.DANGER}
|
||||
isOpen={isOpen}
|
||||
onCancel={handleCancelDeleteAlert}
|
||||
onConfirm={handleConfirmProjectDelete}
|
||||
onConfirm={handleConfirmProjectTaskDelete}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
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 { useDeleteProjectTimeEntry } from '../../hooks';
|
||||
|
||||
import withAlertStoreConnect from '@/containers/Alert/withAlertStoreConnect';
|
||||
import withAlertActions from '@/containers/Alert/withAlertActions';
|
||||
|
||||
import { compose } from '@/utils';
|
||||
|
||||
/**
|
||||
* Project timesheet delete alert.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTimesheetDeleteAlert({
|
||||
name,
|
||||
|
||||
// #withAlertStoreConnect
|
||||
isOpen,
|
||||
payload: { timesheetId },
|
||||
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const { mutateAsync: deleteProjectTimeEntryMutate, isLoading } =
|
||||
useDeleteProjectTimeEntry();
|
||||
|
||||
// handle cancel delete alert.
|
||||
const handleCancelDeleteAlert = () => {
|
||||
closeAlert(name);
|
||||
};
|
||||
|
||||
// handleConfirm delete project time sheet.
|
||||
const handleConfirmProjectTimesheetDelete = () => {
|
||||
deleteProjectTimeEntryMutate(timesheetId)
|
||||
.then(() => {
|
||||
AppToaster.show({
|
||||
message: intl.get('project_time_entry.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={handleConfirmProjectTimesheetDelete}
|
||||
loading={isLoading}
|
||||
>
|
||||
<p>
|
||||
<FormattedHTMLMessage
|
||||
id={'project_time_entry.alert.once_delete_this_project'}
|
||||
/>
|
||||
</p>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withAlertStoreConnect(),
|
||||
withAlertActions,
|
||||
)(ProjectTimesheetDeleteAlert);
|
||||
@@ -4,6 +4,9 @@ const ProjectDeleteAlert = React.lazy(() => import('./ProjectDeleteAlert'));
|
||||
const ProjectTaskDeleteAlert = React.lazy(
|
||||
() => import('./ProjectTaskDeleteAlert'),
|
||||
);
|
||||
const ProjectTimesheetDeleteAlert = React.lazy(
|
||||
() => import('./ProjectTimesheetDeleteAlert'),
|
||||
);
|
||||
|
||||
/**
|
||||
* Project alerts.
|
||||
@@ -11,4 +14,5 @@ const ProjectTaskDeleteAlert = React.lazy(
|
||||
export default [
|
||||
{ name: 'project-delete', component: ProjectDeleteAlert },
|
||||
{ name: 'project-task-delete', component: ProjectTaskDeleteAlert },
|
||||
{ name: 'project-timesheet-delete', component: ProjectTimesheetDeleteAlert },
|
||||
];
|
||||
|
||||
@@ -35,7 +35,6 @@ function ProjectDetailActionsBar({
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { projectId } = useProjectDetailContext();
|
||||
|
||||
// Handle new transaction button click.
|
||||
|
||||
@@ -26,7 +26,7 @@ function ProjectTaskProvider({ ...props }) {
|
||||
enabled: !!projectId,
|
||||
});
|
||||
|
||||
console.log(project, 'XX');
|
||||
|
||||
// provider payload.
|
||||
const provider = {
|
||||
project,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useProject } from '../../../hooks';
|
||||
|
||||
const ProjectTimesheetContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Project timesheets data provider.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTimesheetsProvider({ ...props }) {
|
||||
const { id } = useParams();
|
||||
const projectId = parseInt(id, 10);
|
||||
|
||||
// Handle fetch project detail.
|
||||
const { data: project } = useProject(projectId, {
|
||||
enabled: !!projectId,
|
||||
});
|
||||
|
||||
// provider payload.
|
||||
const provider = {
|
||||
projectId,
|
||||
project,
|
||||
};
|
||||
|
||||
return <ProjectTimesheetContext.Provider value={provider} {...props} />;
|
||||
}
|
||||
|
||||
const useProjectTimesheetContext = () =>
|
||||
React.useContext(ProjectTimesheetContext);
|
||||
|
||||
export { ProjectTimesheetsProvider, useProjectTimesheetContext };
|
||||
@@ -3,6 +3,7 @@ import styled from 'styled-components';
|
||||
|
||||
import { ProjectTimesheetsTable } from './ProjectTimesheetsTable';
|
||||
import { ProjectTimesheetsHeader } from './ProjectTimesheetsHeader';
|
||||
import { ProjectTimesheetsProvider } from './ProjectTimesheetsProvider';
|
||||
|
||||
/**
|
||||
* Project Timesheets.
|
||||
@@ -10,12 +11,12 @@ import { ProjectTimesheetsHeader } from './ProjectTimesheetsHeader';
|
||||
*/
|
||||
export default function ProjectTimeSheets() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ProjectTimesheetsProvider>
|
||||
<ProjectTimesheetsHeader />
|
||||
<ProjectTimesheetTableCard>
|
||||
<ProjectTimesheetsTable />
|
||||
</ProjectTimesheetTableCard>
|
||||
</React.Fragment>
|
||||
</ProjectTimesheetsProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ function ProjectTaskFormProvider({
|
||||
},
|
||||
);
|
||||
|
||||
console.log(taskId, 'XX');
|
||||
console.log(projectTask, 'XX');
|
||||
|
||||
const isNewMode = !taskId;
|
||||
// State provider.
|
||||
const provider = {
|
||||
|
||||
@@ -6,12 +6,12 @@ const Schema = Yup.object().shape({
|
||||
date: Yup.date()
|
||||
.label(intl.get('project_time_entry.schema.label.date'))
|
||||
.required(),
|
||||
projectId: Yup.string()
|
||||
.label(intl.get('project_time_entry.schema.label.project_name'))
|
||||
.required(),
|
||||
taskId: Yup.string()
|
||||
.label(intl.get('project_time_entry.schema.label.task_name'))
|
||||
.required(),
|
||||
// projectId: Yup.string()
|
||||
// .label(intl.get('project_time_entry.schema.label.project_name'))
|
||||
// .required(),
|
||||
// taskId: Yup.string()
|
||||
// .label(intl.get('project_time_entry.schema.label.task_name'))
|
||||
// .required(),
|
||||
description: Yup.string().nullable().max(DATATYPES_LENGTH.TEXT),
|
||||
duration: Yup.string()
|
||||
.label(intl.get('project_time_entry.schema.label.duration'))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import moment from 'moment';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent } from '@blueprintjs/core';
|
||||
import { Formik } from 'formik';
|
||||
import { AppToaster } from '@/components';
|
||||
|
||||
@@ -13,7 +14,7 @@ import { compose } from '@/utils';
|
||||
|
||||
const defaultInitialValues = {
|
||||
date: moment(new Date()).format('YYYY-MM-DD'),
|
||||
projectId: '',
|
||||
// projectId: '',
|
||||
taskId: '',
|
||||
description: '',
|
||||
duration: '',
|
||||
@@ -28,7 +29,11 @@ function ProjectTimeEntryForm({
|
||||
closeDialog,
|
||||
}) {
|
||||
// time entry form dialog context.
|
||||
const { dialogName } = useProjectTimeEntryFormContext();
|
||||
const {
|
||||
dialogName,
|
||||
createProjectTimeEntryMutate,
|
||||
editProjectTimeEntryMutate,
|
||||
} = useProjectTimeEntryFormContext();
|
||||
|
||||
// Initial form values
|
||||
const initialValues = {
|
||||
@@ -37,11 +42,21 @@ function ProjectTimeEntryForm({
|
||||
|
||||
// Handles the form submit.
|
||||
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
|
||||
const form = {};
|
||||
const form = {
|
||||
...values,
|
||||
};
|
||||
|
||||
// Handle request response success.
|
||||
const onSuccess = (response) => {
|
||||
AppToaster.show({});
|
||||
AppToaster.show({
|
||||
message: intl.get(
|
||||
true
|
||||
? 'project_time_entry.success_message'
|
||||
: 'project_time_entry.dialog.edit_success_message',
|
||||
),
|
||||
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
closeDialog(dialogName);
|
||||
};
|
||||
|
||||
@@ -53,6 +68,9 @@ function ProjectTimeEntryForm({
|
||||
}) => {
|
||||
setSubmitting(false);
|
||||
};
|
||||
createProjectTimeEntryMutate([values.taskId, form])
|
||||
.then(onSuccess)
|
||||
.catch(onError);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -9,10 +9,10 @@ import {
|
||||
FInputGroup,
|
||||
FDateInput,
|
||||
FTextArea,
|
||||
FEditableText,
|
||||
FieldRequiredHint,
|
||||
FormattedMessage as T,
|
||||
} from '@/components';
|
||||
import { useProjectTimeEntryFormContext } from './ProjectTimeEntryFormProvider';
|
||||
import { TaskSelect, ProjectsSelect } from '../../components';
|
||||
import { momentFormatter } from '@/utils';
|
||||
|
||||
@@ -21,6 +21,9 @@ import { momentFormatter } from '@/utils';
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTimeEntryFormFields() {
|
||||
// time entry form dialog context.
|
||||
const { projectTasks } = useProjectTimeEntryFormContext();
|
||||
|
||||
return (
|
||||
<div className={Classes.DIALOG_BODY}>
|
||||
{/*------------ Project -----------*/}
|
||||
@@ -45,7 +48,7 @@ function ProjectTimeEntryFormFields() {
|
||||
>
|
||||
<TaskSelect
|
||||
name={'taskId'}
|
||||
tasks={[]}
|
||||
tasks={projectTasks}
|
||||
popoverProps={{ minimal: true }}
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
@@ -18,8 +18,10 @@ function ProjectTimeEntryFormFloatingActions({
|
||||
const { dialogName } = useProjectTimeEntryFormContext();
|
||||
|
||||
// Formik context.
|
||||
const { isSubmitting } = useFormikContext();
|
||||
|
||||
const { isSubmitting, values, errors } = useFormikContext();
|
||||
console.log(values, 'XX');
|
||||
console.log(errors, 'XX');
|
||||
|
||||
// Handle close button click.
|
||||
const handleCancelBtnClick = () => {
|
||||
closeDialog(dialogName);
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
useProjectTasks,
|
||||
useCreateProjectTimeEntry,
|
||||
useEditProjectTimeEntry,
|
||||
} from '../../hooks';
|
||||
import { DialogContent } from '@/components';
|
||||
|
||||
const ProjecctTimeEntryFormContext = React.createContext();
|
||||
@@ -13,12 +18,30 @@ function ProjectTimeEntryFormProvider({
|
||||
projectId,
|
||||
...props
|
||||
}) {
|
||||
// Create and edit project time entry mutations.
|
||||
const { mutateAsync: createProjectTimeEntryMutate } =
|
||||
useCreateProjectTimeEntry();
|
||||
const { mutateAsync: editProjectTimeEntryMutate } = useEditProjectTimeEntry();
|
||||
|
||||
// Handle fetch project tasks.
|
||||
const {
|
||||
data: { projectTasks },
|
||||
isLoading: isProjectTasksLoading,
|
||||
} = useProjectTasks(projectId, {
|
||||
enabled: !!projectId,
|
||||
});
|
||||
|
||||
// provider payload.
|
||||
const provider = {
|
||||
dialogName,
|
||||
projectId,
|
||||
projectTasks,
|
||||
createProjectTimeEntryMutate,
|
||||
editProjectTimeEntryMutate,
|
||||
};
|
||||
|
||||
return (
|
||||
<DialogContent>
|
||||
<DialogContent name={'project-time-entry-form'}>
|
||||
<ProjecctTimeEntryFormContext.Provider value={provider} {...props} />
|
||||
</DialogContent>
|
||||
);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './projects'
|
||||
export * from './projectsTask'
|
||||
export * from './projectsTask'
|
||||
export * from './projectTimeEntry'
|
||||
120
src/containers/Projects/hooks/projectTimeEntry.tsx
Normal file
120
src/containers/Projects/hooks/projectTimeEntry.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
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 entries.
|
||||
queryClient.invalidateQueries(t.PROJECT_TIME_ENTRIES);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new project time entry.
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function useCreateProjectTimeEntry(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.post(`/projects/tasks/${id}/times`, values),
|
||||
{
|
||||
onSuccess: () => {
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit the given project time entry.
|
||||
* @param props
|
||||
* @returns
|
||||
*/
|
||||
export function useEditProjectTimeEntry(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation(
|
||||
([id, values]) => apiRequest.post(`projects/times/${id}`, values),
|
||||
{
|
||||
onSuccess: (res, [id, values]) => {
|
||||
// Invalidate specific project time entry.
|
||||
queryClient.invalidateQueries([t.PROJECT_TIME_ENTRY, id]);
|
||||
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the given project time entry
|
||||
* @param props
|
||||
*/
|
||||
export function useDeleteProjectTimeEntry(props) {
|
||||
const queryClient = useQueryClient();
|
||||
const apiRequest = useApiRequest();
|
||||
|
||||
return useMutation((id) => apiRequest.delete(`projects/times/${id}`), {
|
||||
onSuccess: (res, id) => {
|
||||
// Invalidate specific project task.
|
||||
queryClient.invalidateQueries([t.PROJECT_TASK, id]);
|
||||
|
||||
// Common invalidate queries.
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrive the given project time entry.
|
||||
* @param timeId
|
||||
* @param props
|
||||
* @param requestProps
|
||||
* @returns
|
||||
*/
|
||||
export function useProjectTimeEntry(timeId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.PROJECT_TIME_ENTRY, timeId],
|
||||
{ method: 'get', url: `projects/times/${timeId}`, ...requestProps },
|
||||
{
|
||||
select: (res) => res.data.time,
|
||||
defaultData: {},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const transformProjectTimeEntries = (res) => ({
|
||||
projectTasks: res.data.times,
|
||||
});
|
||||
|
||||
/**
|
||||
*
|
||||
* @param taskId - Task id.
|
||||
* @param props
|
||||
* @param requestProps
|
||||
* @returns
|
||||
*/
|
||||
export function useProjectTimeEntries(taskId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.PROJECT_TIME_ENTRIES, taskId],
|
||||
{ method: 'get', url: `projects/tasks/${taskId}/times`, ...requestProps },
|
||||
{
|
||||
select: transformProjectTimeEntries,
|
||||
defaultData: {
|
||||
projectTimeEntries: [],
|
||||
},
|
||||
...props,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -40,18 +40,15 @@ 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]);
|
||||
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,
|
||||
commonInvalidateQueries(queryClient);
|
||||
},
|
||||
);
|
||||
...props,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,7 +80,7 @@ export function useDeleteProjectTask(props) {
|
||||
*/
|
||||
export function useProjectTask(taskId, props, requestProps) {
|
||||
return useRequestQuery(
|
||||
[t.PROJECT, taskId],
|
||||
[t.PROJECT_TASK, taskId],
|
||||
{ method: 'get', url: `tasks/${taskId}`, ...requestProps },
|
||||
{
|
||||
select: (res) => res.data.task,
|
||||
|
||||
@@ -8,9 +8,19 @@ const PROJECTS = {
|
||||
PROJECTS: 'PROJECTS',
|
||||
};
|
||||
|
||||
const PROJECT_TASKS ={
|
||||
PROJECT_TASKS:'PROJECT_TASKS',
|
||||
PROJECT_TASK:'PROJECT_TASK',
|
||||
}
|
||||
const PROJECT_TASKS = {
|
||||
PROJECT_TASKS: 'PROJECT_TASKS',
|
||||
PROJECT_TASK: 'PROJECT_TASK',
|
||||
};
|
||||
|
||||
export default { ...PROJECTS, ...CUSTOMERS,...PROJECT_TASKS };
|
||||
const PROJECT_TIME_ENTRIES = {
|
||||
PROJECT_TIME_ENTRIES: 'PROJECT_TIME_ENTRIES',
|
||||
PROJECT_TIME_ENTRY: 'PROJECT_TIME_ENTRY',
|
||||
};
|
||||
|
||||
export default {
|
||||
...PROJECTS,
|
||||
...CUSTOMERS,
|
||||
...PROJECT_TASKS,
|
||||
...PROJECT_TIME_ENTRIES,
|
||||
};
|
||||
|
||||
@@ -2124,6 +2124,10 @@
|
||||
"project_time_entry.schema.label.task_name": "Task name",
|
||||
"project_time_entry.schema.label.duration": "Duration",
|
||||
"project_time_entry.schema.label.date": "Date",
|
||||
"project_time_entry.success_message": "The time entry has been created successfully.",
|
||||
"project_time_entry.edit_success_message": "The time entry has been edited successfully.",
|
||||
"project_time_entry.alert.delete_message": "The deleted time entry has been deleted successfully.",
|
||||
"project_time_entry.alert.once_delete_this_project": "Once you delete this time entry, you won't be able to restore it later. Are you sure you want to delete this time entry?",
|
||||
"find_or_choose_a_project": "Find or choose a project",
|
||||
"choose_a_task": "Choose a task",
|
||||
"project_expense.dialog.label": "New Expense",
|
||||
|
||||
Reference in New Issue
Block a user