mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
fix: project form.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { MenuItem, Button } from '@blueprintjs/core';
|
||||
import { FSelect, FFormGroup } from 'components';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} query
|
||||
* @param {*} project
|
||||
* @param {*} _index
|
||||
* @param {*} exactMatch
|
||||
* @returns
|
||||
*/
|
||||
const projectItemPredicate = (query, project, _index, exactMatch) => {
|
||||
const normalizedTitle = project.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${project.name}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} project
|
||||
* @param {*} param1
|
||||
* @returns
|
||||
*/
|
||||
const projectItemRenderer = (project, { handleClick, modifiers, query }) => {
|
||||
return (
|
||||
<MenuItem
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
key={project.id}
|
||||
onClick={handleClick}
|
||||
text={project.name}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const projectSelectProps = {
|
||||
itemPredicate: projectItemPredicate,
|
||||
itemRenderer: projectItemRenderer,
|
||||
valueAccessor: 'id',
|
||||
labelAccessor: 'name',
|
||||
};
|
||||
|
||||
export function ProjectSelect({ projects, ...rest }) {
|
||||
return (
|
||||
<FSelect
|
||||
items={projects}
|
||||
{...projectSelectProps}
|
||||
{...rest}
|
||||
input={ProjectSelectButton}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function ProjectSelectButton({ label }) {
|
||||
return <Button text={label ? label : intl.get('find_or_choose_a_project')} />;
|
||||
}
|
||||
@@ -37,7 +37,7 @@ function ProjectFormFields() {
|
||||
<FastField name={'contact'}>
|
||||
{({ form, field: { value }, meta: { error, touched } }) => (
|
||||
<FormGroup
|
||||
label={intl.get('projects.label.contact')}
|
||||
label={intl.get('projects.dialog.contact')}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
intent={inputIntent({ error, touched })}
|
||||
>
|
||||
@@ -56,14 +56,14 @@ function ProjectFormFields() {
|
||||
</FastField>
|
||||
{/*------------ Project Name -----------*/}
|
||||
<FFormGroup
|
||||
label={intl.get('projects.label.project_name')}
|
||||
label={intl.get('projects.dialog.project_name')}
|
||||
name={'projectName'}
|
||||
>
|
||||
<FInputGroup name="projectName" />
|
||||
</FFormGroup>
|
||||
{/*------------ DeadLine -----------*/}
|
||||
<FFormGroup
|
||||
label={intl.get('projects.label.deadline')}
|
||||
label={intl.get('projects.dialog.deadline')}
|
||||
name={'projectDeadline'}
|
||||
className={classNames(CLASSES.FILL, 'form-group--date')}
|
||||
>
|
||||
@@ -82,13 +82,13 @@ function ProjectFormFields() {
|
||||
<FFormGroup name={'projectState'}>
|
||||
<FCheckbox
|
||||
name="projectState"
|
||||
label={intl.get('projects.label.calculator_expenses')}
|
||||
label={intl.get('projects.dialog.calculator_expenses')}
|
||||
/>
|
||||
</FFormGroup>
|
||||
{/*------------ Cost Estimate -----------*/}
|
||||
<FFormGroup
|
||||
name={'projectCost'}
|
||||
label={intl.get('projects.label.cost_estimate')}
|
||||
label={intl.get('projects.dialog.cost_estimate')}
|
||||
>
|
||||
<ControlGroup>
|
||||
<InputPrependText text={'USD'} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { DataTable } from 'components';
|
||||
import { TABLES } from 'common/tables';
|
||||
@@ -16,21 +17,21 @@ import { compose } from 'utils';
|
||||
const projects = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Project 1',
|
||||
description: 'Project 1 description',
|
||||
status: 'Active',
|
||||
name: 'Maroon Bronze',
|
||||
deadline: '2022-06-08T22:00:00.000Z',
|
||||
display_name: 'Kyrie Rearden',
|
||||
cost_estimate: '4000',
|
||||
task_amount: '1000',
|
||||
is_in_process: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Project 2',
|
||||
description: 'Project 2 description',
|
||||
status: 'Active',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Project 3',
|
||||
description: 'Project 3 description',
|
||||
status: 'Active',
|
||||
name: 'Project Sherwood',
|
||||
deadline: '2022-06-08T22:00:00.000Z',
|
||||
display_name: 'Ella-Grace Miller',
|
||||
cost_estimate: '0',
|
||||
task_amount: '1000',
|
||||
is_in_process: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -81,16 +82,16 @@ function ProjectsDataTable({
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
<ProjectsTable
|
||||
columns={columns}
|
||||
data={projects}
|
||||
// loading={}
|
||||
// headerLoading={}
|
||||
// progressBarLoading={}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
hideTableHeader={true}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
ContextMenu={ActionsMenu}
|
||||
@@ -114,3 +115,40 @@ export default compose(
|
||||
projectsTableSize: projectSettings?.tableSize,
|
||||
})),
|
||||
)(ProjectsDataTable);
|
||||
|
||||
const ProjectsTable = styled(DataTable)`
|
||||
.tbody {
|
||||
.tr .td {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
|
||||
.avatar.td {
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
background: #adbcc9;
|
||||
border-radius: 8%;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
color: #fff;
|
||||
|
||||
&[data-size='medium'] {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 14px;
|
||||
}
|
||||
&[data-size='small'] {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
line-height: 25px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.table-size--small {
|
||||
.tbody .tr {
|
||||
height: 45px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuDivider,
|
||||
MenuItem,
|
||||
Tag,
|
||||
Intent,
|
||||
ProgressBar,
|
||||
} from '@blueprintjs/core';
|
||||
import { Icon, FormatDate, Choose, FormattedMessage as T } from 'components';
|
||||
import { safeCallback, firstLettersArgs, calculateStatus } from 'utils';
|
||||
|
||||
/**
|
||||
* project status.
|
||||
*/
|
||||
export function ProjectStatus({ project }) {
|
||||
return (
|
||||
<ProjectStatusRoot>
|
||||
{project.task_amount}
|
||||
<ProjectProgressBar
|
||||
animate={false}
|
||||
intent={Intent.NONE}
|
||||
value={calculateStatus(project.task_amount, project.cost_estimate)}
|
||||
/>
|
||||
</ProjectStatusRoot>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* project status accessor.
|
||||
*/
|
||||
export const ProjectStatusAccessor = (row) => {
|
||||
return (
|
||||
<Choose>
|
||||
<Choose.When condition={row.is_in_process}>
|
||||
<ProjectStatus project={row} />
|
||||
</Choose.When>
|
||||
<Choose.Otherwise>
|
||||
<Tag round={true} minimal={true}>
|
||||
<T id={'draft'} />
|
||||
</Tag>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Avatar cell.
|
||||
*/
|
||||
export const AvatarCell = ({ row: { original }, size }) => (
|
||||
<span className="avatar" data-size={size}>
|
||||
{firstLettersArgs(original?.display_name, original?.name)}
|
||||
</span>
|
||||
);
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
export const ActionsMenu = ({
|
||||
row: { original },
|
||||
payload: { onEdit, onDelete, onViewDetails, onNewTask },
|
||||
}) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={intl.get('projects.action.edit_project')}
|
||||
onClick={safeCallback(onEdit, original)}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={<Icon icon="plus" />}
|
||||
text={intl.get('projects.action.new_task')}
|
||||
onClick={safeCallback(onNewTask, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={intl.get('projects.action.delete_project')}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
/**
|
||||
* Projects accessor.
|
||||
*/
|
||||
export const ProjectsAccessor = (row) => (
|
||||
<ProjectItemsWrap>
|
||||
<ProjectItemsHeader>
|
||||
<ProjectItemContactName>{row.display_name}</ProjectItemContactName>
|
||||
<ProjectItemProjectName>{row.name}</ProjectItemProjectName>
|
||||
</ProjectItemsHeader>
|
||||
<ProjectItemDescription>
|
||||
<FormatDate value={row.deadline} />
|
||||
{intl.get('projects.label.cost_estimate', {
|
||||
value: row.cost_estimate,
|
||||
})}
|
||||
</ProjectItemDescription>
|
||||
</ProjectItemsWrap>
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieve projects list columns columns.
|
||||
*/
|
||||
export const useProjectsListColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'avatar',
|
||||
Header: '',
|
||||
Cell: AvatarCell,
|
||||
className: 'avatar',
|
||||
width: 45,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Project Name',
|
||||
accessor: ProjectsAccessor,
|
||||
width: 240,
|
||||
className: 'name',
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
Header: 'status',
|
||||
accessor: ProjectStatusAccessor,
|
||||
width: 80,
|
||||
className: 'status',
|
||||
clickable: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
|
||||
const ProjectItemsWrap = styled.div``;
|
||||
|
||||
const ProjectItemsHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
`;
|
||||
|
||||
const ProjectItemContactName = styled.div`
|
||||
font-weight: 600;
|
||||
padding-right: 4px;
|
||||
`;
|
||||
const ProjectItemProjectName = styled.div``;
|
||||
|
||||
const ProjectItemDescription = styled.div`
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
opacity: 0.75;
|
||||
margin-top: 4px;
|
||||
`;
|
||||
|
||||
const ProjectStatusRoot = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row-reverse;
|
||||
margin: 0 20px;
|
||||
`;
|
||||
|
||||
const ProjectProgressBar = styled(ProgressBar)`
|
||||
&.bp3-progress-bar {
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
height: 3px;
|
||||
max-width: 130px;
|
||||
|
||||
&,
|
||||
.bp3-progress-meter {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,60 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
import { Menu, MenuDivider, MenuItem, Intent } from '@blueprintjs/core';
|
||||
|
||||
import { Icon } from 'components';
|
||||
import { safeCallback } from 'utils';
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
export const ActionsMenu = ({
|
||||
row: { original },
|
||||
payload: { onEdit, onDelete, onViewDetails, onNewTask },
|
||||
}) => (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
icon={<Icon icon="reader-18" />}
|
||||
text={intl.get('view_details')}
|
||||
onClick={safeCallback(onViewDetails, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={intl.get('projects.action.edit_project')}
|
||||
onClick={safeCallback(onEdit, original)}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={<Icon icon="plus" />}
|
||||
text={intl.get('projects.action.new_task')}
|
||||
onClick={safeCallback(onNewTask, original)}
|
||||
/>
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text={intl.get('projects.action.delete_project')}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
/**
|
||||
* Retrieve projects list columns columns.
|
||||
*/
|
||||
export const useProjectsListColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Project Name',
|
||||
accessor: 'name',
|
||||
width: 100,
|
||||
className: 'name',
|
||||
clickable: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
@@ -2050,12 +2050,13 @@
|
||||
"projects.action.new_task": "New Task",
|
||||
"projects.action.delete_project": "Delete Project",
|
||||
"projects.label.new_project": "New Project",
|
||||
"projects.label.contact": "Contact",
|
||||
"projects.label.project_name": "Project Name",
|
||||
"projects.label.deadline": "Deadline",
|
||||
"projects.label.calculator_expenses": "Calculator from tasks & estimated expenses",
|
||||
"projects.label.cost_estimate": "Cost Estimate",
|
||||
"projects.dialog.contact": "Contact",
|
||||
"projects.dialog.project_name": "Project Name",
|
||||
"projects.dialog.deadline": "Deadline",
|
||||
"projects.dialog.calculator_expenses": "Calculator from tasks & estimated expenses",
|
||||
"projects.dialog.cost_estimate": "Cost Estimate",
|
||||
"projects.label.create": "Create",
|
||||
"projects.label.cost_estimate":" • Estimate {value}",
|
||||
"task.label.new_task": "New Task",
|
||||
"task.label.task_name": "Task Name",
|
||||
"task.label.estimated_hours": "Task Name",
|
||||
|
||||
Reference in New Issue
Block a user