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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
// @ts-nocheck
import React from 'react';
import { useProjectBillableEntries } from '../../hooks';
import { DialogContent } from '@/components';
const ProjectBillableEntriesFormContext = React.createContext();
@@ -16,31 +15,14 @@ function ProjectBillableEntriesFormProvider({
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}>
<DialogContent>
<ProjectBillableEntriesFormContext.Provider value={provider} {...props} />
</DialogContent>
);

View File

@@ -4,7 +4,10 @@ import intl from 'react-intl-universal';
export const taskChargeOptions = [
{ 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.non_chargeable'), value: 'NON_CHARGABLE' },
{
name: intl.get('project_task.dialog.non_chargeable'),
value: 'NON_CHARGABLE',
},
];
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.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) {
return useRequestQuery(
[t.PROJECT_BILLABLE_ENTRIES, projectId],
[t.PROJECT_BILLABLE_ENTRIES, projectId, query],
{
method: 'get',
url: `projects/${projectId}/billable/entries`,
@@ -20,9 +20,7 @@ export function useProjectBillableEntries(projectId, query, props) {
},
{
select: (res) => res.data.billable_entries,
defaultData: {
billableEntries: [],
},
defaultData: {},
...props,
},
);

View File

@@ -2212,5 +2212,8 @@
"project_invoicing.dialog.bill_to": "Bill To",
"project_billable_entries.dialog.label": "Add Project Entries",
"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"
}