feat: add project billable entries dialog.

This commit is contained in:
elforjani13
2022-09-19 22:37:11 +02:00
parent 2ae720821e
commit 01e2c24387
14 changed files with 445 additions and 2 deletions

View File

@@ -46,7 +46,7 @@ import ProjectTimeEntryFormDialog from '@/containers/Projects/containers/Project
import ProjectExpenseForm from '@/containers/Projects/containers/ProjectExpenseForm';
import EstimatedExpenseFormDialog from '@/containers/Projects/containers/EstimatedExpenseFormDialog';
import ProjectInvoicingFormDialog from '@/containers/Projects/containers/ProjectInvoicingFormDialog';
import ProjectBillableEntriesFormDialog from '@/containers/Projects/containers/ProjectBillableEntriesFormDialog';
import { DialogsName } from '@/constants/dialogs';
/**
@@ -134,6 +134,7 @@ export default function DialogsContainer() {
<ProjectInvoicingFormDialog
dialogName={DialogsName.ProjectInvoicingForm}
/>
<ProjectBillableEntriesFormDialog dialogName={DialogsName.ProjectBillableEntriesForm}/>
</div>
);
}

View File

@@ -44,4 +44,5 @@ export enum DialogsName {
ProjectExpenseForm = 'project-expense-form',
EstimateExpenseForm = 'estimate-expense-form',
ProjectInvoicingForm = 'project-invoicing-form',
ProjectBillableEntriesForm = 'project-billable-entries',
}

View File

@@ -0,0 +1,6 @@
import * as Yup from 'yup';
import intl from 'react-intl-universal';
const Schema = Yup.object().shape({});
export const ProjectBillableEntriesFormSchema = Schema;

View File

@@ -0,0 +1,54 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Formik } from 'formik';
import { AppToaster } from '@/components';
import { ProjectBillableEntriesFormSchema } from './ProjectBillableEntriesForm.schema';
import ProjectBillableEntriesFormContent from './ProjectBillableEntriesFormContent';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
const defaultInitialValues = {};
/**
* project billable entries form.
* @returns
*/
function ProjectBillableEntriesForm({
//#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={ProjectBillableEntriesFormSchema}
initialValues={initialValues}
onSubmit={handleFormSubmit}
component={ProjectBillableEntriesFormContent}
/>
);
}
export default compose(withDialogActions)(ProjectBillableEntriesForm);

View File

@@ -0,0 +1,17 @@
import React from 'react';
import { Form } from 'formik';
import ProjectBillableEntriesFormFields from './ProjectBillableEntriesFormFields';
import ProjectBillableEntriesFormFloatingActions from './ProjectBillableEntriesFormFloatingActions';
/**
* Project billable entries form content.
* @returns
*/
export default function ProjectBillableEntriesFormContent() {
return (
<Form>
<ProjectBillableEntriesFormFields />
<ProjectBillableEntriesFormFloatingActions />
</Form>
);
}

View File

@@ -0,0 +1,22 @@
import React from 'react';
import { ProjectBillableEntriesFormProvider } from './ProjectBillableEntriesFormProvider';
import ProjectBillableEntriesForm from './ProjectBillableEntriesForm';
/**
* Project billable entries form dialog content.
* @returns
*/
export default function ProjectEntriesFormDialogContent({
// #ownProps
dialogName,
projectId,
}) {
return (
<ProjectBillableEntriesFormProvider
dialogName={dialogName}
projectId={projectId}
>
<ProjectBillableEntriesForm />
</ProjectBillableEntriesFormProvider>
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { useFormikContext } from 'formik';
import { Classes } from '@blueprintjs/core';
import {
FFormGroup,
FInputGroup,
FieldRequiredHint,
FormattedMessage as T,
} from '@/components';
import { ProjectRowDivider, ProjectEntiresBox } from './components';
import { useProjectBillableEntriesFormContext } from './ProjectBillableEntriesFormProvider';
/**
* Project billable entries form fields.
* @returns
*/
export default function ProjectBillableEntriesFormFields() {
// Formik context.
const { values } = useFormikContext();
const { billableEntries } = useProjectBillableEntriesFormContext();
return (
<div className={Classes.DIALOG_BODY}>
{/*------------ Filter by Date -----------*/}
<FFormGroup
name={'date'}
label={<T id={'project_billable_entries.dialog.filter_by_date'} />}
labelInfo={<FieldRequiredHint />}
>
<FInputGroup name="date" placeholder={'Placeholder text'} />
</FFormGroup>
<ProjectRowDivider />
{/*------------ Filter by Type -----------*/}
<FFormGroup
name={'type'}
label={<T id={'project_billable_entries.dialog.filter_by_type'} />}
labelInfo={<FieldRequiredHint />}
>
<FInputGroup name="type" placeholder={'Placeholder Type'} />
</FFormGroup>
<ProjectEntiresBox billableEntries={billableEntries} />
</div>
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import styled from 'styled-components';
import { useFormikContext } from 'formik';
import { Intent, Button, Classes } from '@blueprintjs/core';
import { FormattedMessage as T } from '@/components';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { compose } from '@/utils';
/**
* project entries from floating actions.
* @return
*/
function ProjectEntriesFormFloatingActions({
// #withDialogActions
closeDialog,
}) {
// Formik context.
const { isSubmitting } = useFormikContext();
// Handle close button click.
const handleCancelBtnClick = () => {
closeDialog(dialogName);
};
return (
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<SaveButton
intent={Intent.PRIMARY}
loading={isSubmitting}
type="submit"
>
Save
</SaveButton>
</div>
</div>
);
}
export default compose(withDialogActions)(ProjectEntriesFormFloatingActions);
const SaveButton = styled(Button)`
&.bp3-button {
min-width: 80px;
border-radius: 16px;
margin-left: 0px;
}
`;

View File

@@ -0,0 +1,53 @@
import React from 'react';
import { useProjectBillableEntries } from '../../hooks';
import { DialogContent } from '@/components';
const ProjectBillableEntriesFormContext = React.createContext();
/**
* Project billable entries form provider.
* @returns
*/
function ProjectBillableEntriesFormProvider({
// #ownProps
dialogName,
projectId,
...props
}) {
// Handle fetch project billable entries.
const {
data: { billableEntries },
isLoading: isProjectBillableEntriesLoading,
} = useProjectBillableEntries(
projectId,
{
billable_type: 'expense',
to_date: '',
},
{
enabled: !!projectId,
keepPreviousData: true,
},
);
//state provider.
const provider = {
dialogName,
projectId,
billableEntries,
};
return (
<DialogContent isLoading={isProjectBillableEntriesLoading}>
<ProjectBillableEntriesFormContext.Provider value={provider} {...props} />
</DialogContent>
);
}
const useProjectBillableEntriesFormContext = () =>
React.useContext(ProjectBillableEntriesFormContext);
export {
ProjectBillableEntriesFormProvider,
useProjectBillableEntriesFormContext,
};

View File

@@ -0,0 +1,102 @@
import React from 'react';
import { Button } from '@blueprintjs/core';
import intl from 'react-intl-universal';
import styled from 'styled-components';
/**
* Projec billable entries item box.
* @returns
*/
function ProjectBillableEntriesItemBox({ projectBillableEntry }) {
return (
<ProjectEntryBox>
<ProjectEntryHeader>
<ProjectEntryTitle>{projectBillableEntry.title}</ProjectEntryTitle>
<ProjectEntrtyItemContent>
<ProjectEntryItem>{projectBillableEntry.date}</ProjectEntryItem>
<ProjectEntryItem>{projectBillableEntry.time}</ProjectEntryItem>
</ProjectEntrtyItemContent>
</ProjectEntryHeader>
<ProjectEntryContent>
<ProjectEntryAmount>{projectBillableEntry.billable_amount}</ProjectEntryAmount>
</ProjectEntryContent>
<ProjectEntryFoorer>
<ProjectEntryButton small={true}>Add</ProjectEntryButton>
<ProjectEntryButton small={true}>Show</ProjectEntryButton>
</ProjectEntryFoorer>
</ProjectEntryBox>
);
}
/**
* Project billable entries box.
* @returns
*/
export function ProjectEntiresBox({ billableEntries }) {
return billableEntries.map((entries) => (
<ProjectBillableEntriesItemBox projectBillableEntry={entries} />
));
}
const ProjectEntryBox = styled.div`
display: flex;
flex-direction: column;
border-radius: 5px;
width: 360px;
height: 121px;
border: 1px solid #d4d9df;
padding: 15px 12px;
margin-bottom: 15px;
position: relative;
`;
const ProjectEntryHeader = styled.div``;
const ProjectEntryTitle = styled.div`
font-size: 14px;
line-height: 1.5;
font-weight: 500;
color: #444444;
`;
const ProjectEntrtyItemContent = styled.div`
display: flex;
justify-content: space-between;
`;
const ProjectEntryItem = styled.div`
font-weight: 400;
font-size: 10px;
color: #666666;
`;
const ProjectEntryContent = styled.div`
flex: 1 0 auto;
line-height: 2rem;
border-bottom: 1px solid #e3e3e3;
`;
const ProjectEntryAmount = styled.div`
font-size: 14px;
font-weight: 500;
color: #111111;
`;
export const ProjectRowDivider = styled.div`
height: 1px;
background: #e3e3e3;
margin-bottom: 15px;
margin-top: 15px;
`;
const ProjectEntryFoorer = styled.div`
padding: 0;
`;
const ProjectEntryButton = styled(Button)`
&.bp3-button.bp3-small,
&.bp3-button:not([class*='bp3-intent-']):not(.bp3-minimal).bp3-small {
font-size: 12px;
color: #2172ed;
background: transparent;
&:last-child {
margin-right: 5px;
}
}
`;

View File

@@ -0,0 +1,58 @@
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 ProjectBillableEntriesFormDialogContent = React.lazy(
() => import('./ProjectBillableEntriesFormDialogContent'),
);
/**
* Project billable entries form dialog.
* @returns
*/
function ProjectBillableEntriesFormDialog({
dialogName,
payload: { projectId },
isOpen,
}) {
return (
<ProjectBillableEntriesFormDialogRoot
name={dialogName}
title={<T id={'project_billable_entries.dialog.label'} />}
isOpen={isOpen}
autoFocus={true}
canEscapeKeyClose={true}
style={{ width: '400px' }}
>
<DialogSuspense>
<ProjectBillableEntriesFormDialogContent
dialogName={dialogName}
projectId={projectId}
/>
</DialogSuspense>
</ProjectBillableEntriesFormDialogRoot>
);
}
export default compose(withDialogRedux())(ProjectBillableEntriesFormDialog);
const ProjectBillableEntriesFormDialogRoot = styled(Dialog)`
.bp3-dialog-body {
.bp3-form-group {
margin-bottom: 15px;
label.bp3-label {
margin-bottom: 3px;
font-size: 13px;
}
}
}
.bp3-dialog-footer {
.bp3-dialog-footer-actions {
display: flex;
justify-content: flex-start;
}
}
`;

View File

@@ -1,3 +1,4 @@
export * from './projects'
export * from './projectsTask'
export * from './projectTimeEntry'
export * from './projectTimeEntry'
export * from './projectBillableEntries'

View File

@@ -0,0 +1,27 @@
import { useRequestQuery } from '@/hooks/useQueryRequest';
import t from './type';
/**
*
* @param projectId - Project id.
* @param query
* @param props
* @returns
*/
export function useProjectBillableEntries(projectId, query, props) {
return useRequestQuery(
[t.PROJECT_BILLABLE_ENTRIES, projectId],
{
method: 'get',
url: `projects/${projectId}/billable/entries`,
params: query,
},
{
select: (res) => res.data.billable_entries,
defaultData: {
billableEntries: [],
},
...props,
},
);
}

View File

@@ -18,9 +18,14 @@ const PROJECT_TIME_ENTRIES = {
PROJECT_TIME_ENTRY: 'PROJECT_TIME_ENTRY',
};
const PROJECT_BILLABLE_ENTRIES = {
PROJECT_BILLABLE_ENTRIES: 'PROJECT_BILLABLE_ENTRIES',
};
export default {
...PROJECTS,
...CUSTOMERS,
...PROJECT_TASKS,
...PROJECT_TIME_ENTRIES,
...PROJECT_BILLABLE_ENTRIES,
};