feat: project billable entries dialog

This commit is contained in:
elforjani13
2022-09-20 18:57:22 +02:00
parent 7ac9f78366
commit 942644f8d5
8 changed files with 169 additions and 49 deletions

View File

@@ -0,0 +1,132 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import { Menu, MenuItem } from '@blueprintjs/core';
import { Suggest } from '@blueprintjs/select';
import { FormattedMessage as T } from '@/components';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
/**
* @param {*} billableType
* @param {*} param1
* @returns {JSX.Element}
*/
const billableTypeItemRenderer = (
billableType,
{ handleClick, modifiers, query },
) => {
return (
<MenuItem
disabled={modifiers.disabled}
key={billableType.id}
text={billableType.name}
onClick={handleClick}
/>
);
};
/**
*
* @param {*} query
* @param {*} billableType
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const billableTypeItemPredicate = (query, billableType, _index, exactMatch) => {
const normalizedTitle = billableType.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return (
`${billableType.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0
);
}
};
/**
*
* @param inputValue
* @returns
*/
const billableTypeInputValueRenderer = (inputValue) => {
if (inputValue) {
return inputValue.name.toString();
}
return '';
};
/**
* Project billable suggest field.
* @param
*/
export function ProjectBillableSuggestField({
billableType,
initialBillableTypeId,
selectedBillableTypeId,
defautlSelectText = 'Placeholder Type...',
onBillableTypeSelected,
popoverFill = false,
...suggestProps
}) {
const initialBillableType = React.useMemo(
() => billableType.find((b) => b.id === initialBillableTypeId),
[initialBillableTypeId, billableType],
);
const [selectedBillableType, setSelectedBillableType] = React.useState(
initialBillableType || null,
);
React.useEffect(() => {
if (typeof selectedBillableTypeId !== 'undefined') {
const billableType = selectedBillableTypeId
? billableType.find((a) => a.id === selectedBillableTypeId)
: null;
setSelectedBillableType(billableType);
}
}, [selectedBillableTypeId, billableType, setSelectedBillableType]);
/**
*
* @param {*} billableType
* @returns
*/
const billableTypetemSelect = React.useCallback(
(billableType) => {
if (billableType.id) {
setSelectedBillableType({ ...billableType });
onBillableTypeSelected && onBillableTypeSelected(billableType);
}
},
[setSelectedBillableType, onBillableTypeSelected],
);
const billableTypeSelectProps = {
itemRenderer: billableTypeItemRenderer,
itemPredicate: billableTypeItemPredicate,
inputValueRenderer: billableTypeInputValueRenderer,
};
return (
<Suggest
items={billableType}
selectedItem={selectedBillableType}
onItemSelect={billableTypetemSelect}
inputProps={{ placeholder: defautlSelectText }}
fill={true}
resetOnClose={true}
popoverProps={{ minimal: true, boundary: 'window' }}
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
{...billableTypeSelectProps}
{...suggestProps}
/>
);
}

View File

@@ -48,7 +48,6 @@ export function ProjectSuggestField({
*/ */
const projectsItemRenderer = (project, { handleClick, modifiers, query }) => { const projectsItemRenderer = (project, { handleClick, modifiers, query }) => {
return ( return (
<MenuContent>
<MenuItem <MenuItem
icon={<AvatarSelect text={project} />} icon={<AvatarSelect text={project} />}
disabled={modifiers.disabled} disabled={modifiers.disabled}
@@ -56,7 +55,6 @@ export function ProjectSuggestField({
text={project.name} text={project.name}
onClick={handleClick} onClick={handleClick}
/> />
</MenuContent>
); );
}; };
@@ -133,20 +131,14 @@ const AvatarSelect = ({ text }) => {
return <AvaterContent>{firstLettersArgs(text?.name)}</AvaterContent>; return <AvaterContent>{firstLettersArgs(text?.name)}</AvaterContent>;
}; };
const MenuContent = styled(Menu)`
.bp3-menu {
margin: 5px;
}
`;
const AvaterContent = styled.div` const AvaterContent = styled.div`
display: inline-block; display: inline-block;
background: #adbcc9; background: #adbcc9;
text-align: center; text-align: center;
font-weight: 400; font-weight: 400;
color: #fff; color: #fff;
height: 25px; height: 22px;
width: 25px; width: 22px;
line-height: 25px; line-height: 22px;
font-size: 12px; font-size: 12px;
`; `;

View File

@@ -6,3 +6,4 @@ export * from './ProjectsSelect';
export * from './ProjectMultiSelect' export * from './ProjectMultiSelect'
export * from './FInputGroupComponent'; export * from './FInputGroupComponent';
export * from './ProjectSuggestField' export * from './ProjectSuggestField'
export * from './ProjectBillableTypeSuggestField'

View File

@@ -9,6 +9,8 @@ import {
FieldRequiredHint, FieldRequiredHint,
FormattedMessage as T, FormattedMessage as T,
} from '@/components'; } from '@/components';
import { ProjectBillableSuggestField } from '../../components';
import { billableTypeOption } from '../common';
import { ProjectRowDivider, ProjectEntiresBox } from './components'; import { ProjectRowDivider, ProjectEntiresBox } from './components';
import { useProjectBillableEntriesFormContext } from './ProjectBillableEntriesFormProvider'; import { useProjectBillableEntriesFormContext } from './ProjectBillableEntriesFormProvider';
@@ -20,8 +22,6 @@ export default function ProjectBillableEntriesFormFields() {
// Formik context. // Formik context.
const { values } = useFormikContext(); const { values } = useFormikContext();
const { billableEntries } = useProjectBillableEntriesFormContext();
return ( return (
<div className={Classes.DIALOG_BODY}> <div className={Classes.DIALOG_BODY}>
{/*------------ Filter by Date -----------*/} {/*------------ Filter by Date -----------*/}
@@ -37,14 +37,14 @@ export default function ProjectBillableEntriesFormFields() {
{/*------------ Filter by Type -----------*/} {/*------------ Filter by Type -----------*/}
<FFormGroup <FFormGroup
name={'type'} name={'billableType'}
label={<T id={'project_billable_entries.dialog.filter_by_type'} />} label={<T id={'project_billable_entries.dialog.filter_by_type'} />}
labelInfo={<FieldRequiredHint />} labelInfo={<FieldRequiredHint />}
> >
<FInputGroup name="type" placeholder={'Placeholder Type'} /> <ProjectBillableSuggestField billableType={billableTypeOption} />
</FFormGroup> </FFormGroup>
<ProjectEntiresBox billableEntries={billableEntries} /> <ProjectEntiresBox billableEntries={[]} />
</div> </div>
); );
} }

View File

@@ -1,7 +1,6 @@
// @ts-nocheck // @ts-nocheck
import React from 'react'; import React from 'react';
import { useProjectBillableEntries } from '../../hooks';
import { DialogContent } from '@/components'; import { DialogContent } from '@/components';
const ProjectBillableEntriesFormContext = React.createContext(); const ProjectBillableEntriesFormContext = React.createContext();
@@ -16,31 +15,14 @@ function ProjectBillableEntriesFormProvider({
projectId, projectId,
...props ...props
}) { }) {
// Handle fetch project billable entries.
const {
data: { billableEntries },
isLoading: isProjectBillableEntriesLoading,
} = useProjectBillableEntries(
projectId,
{
billable_type: 'expense',
to_date: '',
},
{
enabled: !!projectId,
keepPreviousData: true,
},
);
//state provider. //state provider.
const provider = { const provider = {
dialogName, dialogName,
projectId, projectId,
billableEntries,
}; };
return ( return (
<DialogContent isLoading={isProjectBillableEntriesLoading}> <DialogContent>
<ProjectBillableEntriesFormContext.Provider value={provider} {...props} /> <ProjectBillableEntriesFormContext.Provider value={provider} {...props} />
</DialogContent> </DialogContent>
); );

View File

@@ -4,7 +4,10 @@ import intl from 'react-intl-universal';
export const taskChargeOptions = [ export const taskChargeOptions = [
{ name: intl.get('project_task.dialog.hourly_rate'), value: 'TIME' }, { name: intl.get('project_task.dialog.hourly_rate'), value: 'TIME' },
{ name: intl.get('project_task.dialog.fixed_price'), value: 'FIXED' }, { name: intl.get('project_task.dialog.fixed_price'), value: 'FIXED' },
{ name: intl.get('project_task.dialog.non_chargeable'), value: 'NON_CHARGABLE' }, {
name: intl.get('project_task.dialog.non_chargeable'),
value: 'NON_CHARGABLE',
},
]; ];
export const expenseChargeOption = [ export const expenseChargeOption = [
@@ -16,3 +19,12 @@ export const expenseChargeOption = [
{ name: intl.get('expenses.dialog.custom_pirce'), value: 'custom_pirce' }, { name: intl.get('expenses.dialog.custom_pirce'), value: 'custom_pirce' },
{ name: intl.get('expenses.dialog.non_chargeable'), value: 'non_chargeable' }, { name: intl.get('expenses.dialog.non_chargeable'), value: 'non_chargeable' },
]; ];
export const billableTypeOption = [
{ name: intl.get('project_billable_entries.dialog.task'), value: 'Task' },
{ name: intl.get('project_billable_entries.dialog.bill'), value: 'Bill' },
{
name: intl.get('project_billable_entries.dialog.expense'),
value: 'Expense',
},
];

View File

@@ -12,7 +12,7 @@ import t from './type';
*/ */
export function useProjectBillableEntries(projectId, query, props) { export function useProjectBillableEntries(projectId, query, props) {
return useRequestQuery( return useRequestQuery(
[t.PROJECT_BILLABLE_ENTRIES, projectId], [t.PROJECT_BILLABLE_ENTRIES, projectId, query],
{ {
method: 'get', method: 'get',
url: `projects/${projectId}/billable/entries`, url: `projects/${projectId}/billable/entries`,
@@ -20,9 +20,7 @@ export function useProjectBillableEntries(projectId, query, props) {
}, },
{ {
select: (res) => res.data.billable_entries, select: (res) => res.data.billable_entries,
defaultData: { defaultData: {},
billableEntries: [],
},
...props, ...props,
}, },
); );

View File

@@ -2212,5 +2212,8 @@
"project_invoicing.dialog.bill_to": "Bill To", "project_invoicing.dialog.bill_to": "Bill To",
"project_billable_entries.dialog.label": "Add Project Entries", "project_billable_entries.dialog.label": "Add Project Entries",
"project_billable_entries.dialog.filter_by_date": "Filter by Date", "project_billable_entries.dialog.filter_by_date": "Filter by Date",
"project_billable_entries.dialog.filter_by_type":"Filter by Type" "project_billable_entries.dialog.filter_by_type": "Filter by Type",
"project_billable_entries.dialog.expense": "Expense",
"project_billable_entries.dialog.task": "Task",
"project_billable_entries.dialog.bill": "Bill"
} }