mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-21 15:20:34 +00:00
feat(projects): WIP projects service.
This commit is contained in:
2076
package-lock.json
generated
2076
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -107,7 +107,11 @@
|
|||||||
},
|
},
|
||||||
"proxy": "http://localhost:3000/",
|
"proxy": "http://localhost:3000/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react-dom": "^16.9.16"
|
"@types/react-dom": "^16.9.16",
|
||||||
|
"react-error-overlay": "^6.0.9"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"react-error-overlay": "6.0.9"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
|
|||||||
@@ -583,6 +583,17 @@ export const SidebarMenu = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: <T id={'Reports'} />,
|
||||||
|
type: ISidebarMenuItemType.Group,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
text: <T id={'project_profitability_summary'} />,
|
||||||
|
href: '/financial-reports/project-profitability-summary',
|
||||||
|
type: ISidebarMenuItemType.Link,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// ---------------
|
// ---------------
|
||||||
@@ -669,11 +680,6 @@ export const SidebarMenu = [
|
|||||||
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
ability: ReportsAction.READ_AP_AGING_SUMMARY,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: <T id={'project_profitability_summary'} />,
|
|
||||||
href: '/financial-reports/project-profitability-summary',
|
|
||||||
type: ISidebarMenuItemType.Link,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
//@ts-nocheck
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Overlay, OverlayProps } from '@blueprintjs/core';
|
import { Overlay, OverlayProps } from '@blueprintjs/core';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
@@ -19,7 +18,7 @@ export interface ISidebarOverlayProps {
|
|||||||
|
|
||||||
export interface ISidebarOverlayItemProps {
|
export interface ISidebarOverlayItemProps {
|
||||||
text: string | JSX.Element;
|
text: string | JSX.Element;
|
||||||
href?: string;
|
href: string;
|
||||||
onClick?: any;
|
onClick?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ function SidebarOverlayItem({ item }: SidebarOverlayItemProps) {
|
|||||||
) : //
|
) : //
|
||||||
item.type === ISidebarMenuItemType.Link ||
|
item.type === ISidebarMenuItemType.Link ||
|
||||||
item.type === ISidebarMenuItemType.Dialog ? (
|
item.type === ISidebarMenuItemType.Dialog ? (
|
||||||
<SidebarOverlayItemLink text={item.text} onClick={item.onClick} />
|
<SidebarOverlayItemLink text={item.text} href={item.href} onClick={item.onClick} />
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,12 +54,11 @@ const ProjectProfitabilitySummaryDataTable = styled(ReportDataTable)`
|
|||||||
padding-top: 0.32rem;
|
padding-top: 0.32rem;
|
||||||
padding-bottom: 0.32rem;
|
padding-bottom: 0.32rem;
|
||||||
}
|
}
|
||||||
.tr.row_type--total .td {
|
&.row_type--TOTAL .td {
|
||||||
border-top: 1px solid #bbb;
|
border-top: 1px solid #bbb;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border-bottom: 3px double #000;
|
border-bottom: 3px double #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-of-type .td {
|
&:last-of-type .td {
|
||||||
border-bottom: 1px solid #bbb;
|
border-bottom: 1px solid #bbb;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ function ProjectDetailActionsBar({
|
|||||||
transactions={projectTranslations}
|
transactions={projectTranslations}
|
||||||
onItemSelect={handleNewTransactionBtnClick}
|
onItemSelect={handleNewTransactionBtnClick}
|
||||||
/>
|
/>
|
||||||
|
<NavbarDivider />
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon={'time-24'} iconSize={16} />}
|
icon={<Icon icon={'time-24'} iconSize={16} />}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export default function ProjectDetailTabs() {
|
|||||||
renderActiveTabPanelOnly={true}
|
renderActiveTabPanelOnly={true}
|
||||||
defaultSelectedTabId={'tasks'}
|
defaultSelectedTabId={'tasks'}
|
||||||
>
|
>
|
||||||
<Tab id="overview" title={intl.get('project_details.label.overview')} />
|
|
||||||
<Tab
|
<Tab
|
||||||
id="tasks"
|
id="tasks"
|
||||||
title={intl.get('project_details.label.tasks')}
|
title={intl.get('project_details.label.tasks')}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import moment from 'moment';
|
|
||||||
import styled from 'styled-components';
|
|
||||||
import { Intent } from '@blueprintjs/core';
|
|
||||||
import { FormatDate } from '@/components';
|
import { FormatDate } from '@/components';
|
||||||
import {
|
import {
|
||||||
DetailFinancialCard,
|
DetailFinancialCard,
|
||||||
DetailFinancialSection,
|
DetailFinancialSection,
|
||||||
FinancialProgressBar,
|
|
||||||
FinancialCardText,
|
|
||||||
} from './components';
|
} from './components';
|
||||||
import { calculateStatus } from '@/utils';
|
import { calculateStatus } from '@/utils';
|
||||||
import { useCalculateProject } from './utils';
|
import { useCalculateProject } from './utils';
|
||||||
@@ -18,20 +13,11 @@ import { useProjectDetailContext } from './ProjectDetailProvider';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Project details header.
|
* Project details header.
|
||||||
* @returns
|
|
||||||
*/
|
*/
|
||||||
export function ProjectDetailHeader() {
|
export function ProjectDetailHeader() {
|
||||||
const { project } = useProjectDetailContext();
|
const { project } = useProjectDetailContext();
|
||||||
|
|
||||||
const { percentageOfInvoice, percentageOfExpense } = useCalculateProject();
|
const { percentageOfInvoice, percentageOfExpense } = useCalculateProject();
|
||||||
|
|
||||||
// function getDiff() {
|
|
||||||
let start = moment(new Date());
|
|
||||||
let end = moment(project.deadline);
|
|
||||||
let duration = moment.duration(start.diff(end, 'days'));
|
|
||||||
|
|
||||||
console.log(duration, 'XX');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DetailFinancialSection>
|
<DetailFinancialSection>
|
||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
@@ -41,32 +27,25 @@ export function ProjectDetailHeader() {
|
|||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
label={intl.get('project_details.label.invoiced')}
|
label={intl.get('project_details.label.invoiced')}
|
||||||
value={project.total_invoiced_formatted}
|
value={project.total_invoiced_formatted}
|
||||||
>
|
description={intl.get('project_details.label.of_project_estimate', {
|
||||||
<FinancialCardText>
|
|
||||||
{intl.get('project_details.label.of_project_estimate', {
|
|
||||||
value: percentageOfInvoice,
|
value: percentageOfInvoice,
|
||||||
})}
|
})}
|
||||||
</FinancialCardText>
|
progressValue={calculateStatus(
|
||||||
<FinancialProgressBar
|
project.total_invoiced,
|
||||||
intent={Intent.NONE}
|
project.cost_estimate,
|
||||||
value={calculateStatus(project.total_invoiced, project.cost_estimate)}
|
)}
|
||||||
/>
|
/>
|
||||||
</DetailFinancialCard>
|
|
||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
label={intl.get('project_details.label.time_expenses')}
|
label={intl.get('project_details.label.time_expenses')}
|
||||||
value={project.total_expenses_formatted}
|
value={project.total_expenses_formatted}
|
||||||
>
|
description={intl.get('project_details.label.of_project_estimate', {
|
||||||
<FinancialCardText>
|
|
||||||
{intl.get('project_details.label.of_project_estimate', {
|
|
||||||
value: percentageOfExpense,
|
value: percentageOfExpense,
|
||||||
})}
|
})}
|
||||||
</FinancialCardText>
|
progressValue={calculateStatus(
|
||||||
<FinancialProgressBar
|
project.total_expenses,
|
||||||
intent={Intent.NONE}
|
project.cost_estimate,
|
||||||
value={calculateStatus(project.total_expenses, project.cost_estimate)}
|
)}
|
||||||
/>
|
/>
|
||||||
</DetailFinancialCard>
|
|
||||||
|
|
||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
label={intl.get('project_details.label.to_be_invoiced')}
|
label={intl.get('project_details.label.to_be_invoiced')}
|
||||||
value={project.total_billable_formatted}
|
value={project.total_billable_formatted}
|
||||||
@@ -74,9 +53,8 @@ export function ProjectDetailHeader() {
|
|||||||
<DetailFinancialCard
|
<DetailFinancialCard
|
||||||
label={'Deadline'}
|
label={'Deadline'}
|
||||||
value={<FormatDate value={project.deadline_formatted} />}
|
value={<FormatDate value={project.deadline_formatted} />}
|
||||||
>
|
description={'4 days to go'}
|
||||||
<FinancialCardText>4 days to go</FinancialCardText>
|
/>
|
||||||
</DetailFinancialCard>
|
|
||||||
</DetailFinancialSection>
|
</DetailFinancialSection>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,5 +84,21 @@ const ProjectTaksDataTable = styled(DataTable)`
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tbody .tr .td{
|
||||||
|
padding-top: 0.7rem;
|
||||||
|
padding-bottom: 0.7rem;
|
||||||
|
|
||||||
|
&:first-of-type{
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
&.td-actions{
|
||||||
|
padding-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tbody .tr:last-of-type .td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import intl from 'react-intl-universal';
|
import intl from 'react-intl-universal';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Icon, If, Choose, FormattedMessage as T } from '@/components';
|
import { Icon, If, Choose, FormattedMessage as T } from '@/components';
|
||||||
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
import { Menu, MenuItem, Intent, ProgressBar } from '@blueprintjs/core';
|
||||||
import { safeCallback } from '@/utils';
|
import { safeCallback } from '@/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,25 +70,72 @@ export function TaskAccessor(task) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TaskRoot = styled.div`
|
export function TaskTimeAccessor(task) {
|
||||||
margin-left: 12px;
|
return (
|
||||||
|
<TaskTimeRoot>
|
||||||
|
<TaskTimeMinutesRoot>
|
||||||
|
<TaskTimeMinutes>00:00</TaskTimeMinutes>
|
||||||
|
<TaskTimeFull>17h 30m</TaskTimeFull>
|
||||||
|
</TaskTimeMinutesRoot>
|
||||||
|
|
||||||
|
<TaskProgressBar
|
||||||
|
animate={false}
|
||||||
|
stripes={false}
|
||||||
|
intent={Intent.NONE}
|
||||||
|
value={100}
|
||||||
|
/>
|
||||||
|
</TaskTimeRoot>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TaskTimeRoot = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: row-reverse;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const TaskTimeMinutesRoot = styled.div`
|
||||||
|
margin-left: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: right;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TaskTimeMinutes = styled.div``;
|
||||||
|
const TaskTimeFull = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
color: #5b5c62;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TaskProgressBar = styled(ProgressBar)`
|
||||||
|
&.bp3-progress-bar {
|
||||||
|
display: block;
|
||||||
|
flex-shrink: 0;
|
||||||
|
height: 4px;
|
||||||
|
max-width: 150px;
|
||||||
|
&,
|
||||||
|
.bp3-progress-meter {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TaskRoot = styled.div``;
|
||||||
const TaskHeader = styled.div`
|
const TaskHeader = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
flex-flow: wrap;
|
flex-flow: wrap;
|
||||||
`;
|
`;
|
||||||
const TaskTitle = styled.span`
|
const TaskTitle = styled.span`
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
line-height: 1.5rem;
|
|
||||||
`;
|
`;
|
||||||
const TaskContent = styled.div`
|
const TaskContent = styled.div`
|
||||||
display: block;
|
display: block;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
margin-bottom: 0.1rem;
|
margin-top: 0.25rem;
|
||||||
line-height: 1.2rem;
|
|
||||||
`;
|
`;
|
||||||
const TaskDescription = styled.span`
|
const TaskDescription = styled.span`
|
||||||
margin: 0.3rem;
|
margin: 0.3rem;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { TaskAccessor } from './components';
|
import { TaskAccessor, TaskTimeAccessor } from './components';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve project tasks list columns.
|
* Retrieve project tasks list columns.
|
||||||
@@ -17,6 +17,15 @@ export function useProjectTaskColumns() {
|
|||||||
clickable: true,
|
clickable: true,
|
||||||
textOverview: true,
|
textOverview: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'actions',
|
||||||
|
Header: 'Header',
|
||||||
|
accessor: TaskTimeAccessor,
|
||||||
|
width: 100,
|
||||||
|
className: 'name',
|
||||||
|
clickable: true,
|
||||||
|
textOverview: true,
|
||||||
|
}
|
||||||
],
|
],
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,28 +1,37 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { isUndefined } from 'lodash';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ProgressBar } from '@blueprintjs/core';
|
import { Intent, ProgressBar } from '@blueprintjs/core';
|
||||||
|
|
||||||
export function DetailFinancialSection({ children }) {
|
export function DetailFinancialSection({ children }) {
|
||||||
return <FinancialSectionWrap>{children}</FinancialSectionWrap>;
|
return <FinancialSectionWrap>{children}</FinancialSectionWrap>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DetailFinancialCard({ label, value, children }) {
|
interface DetailFinancialCardProps {
|
||||||
|
label: string;
|
||||||
|
value: number;
|
||||||
|
description: string | JSX.Element;
|
||||||
|
progressValue: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DetailFinancialCard({
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
description,
|
||||||
|
progressValue,
|
||||||
|
}: DetailFinancialCardProps) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
|
||||||
<FinancialSectionCard>
|
<FinancialSectionCard>
|
||||||
<FinancialSectionCardContent>
|
|
||||||
<FinancialCardTitle>{label}</FinancialCardTitle>
|
<FinancialCardTitle>{label}</FinancialCardTitle>
|
||||||
<FinancialCardValue>{value}</FinancialCardValue>
|
<FinancialCardValue>{value}</FinancialCardValue>
|
||||||
{children}
|
{description && <FinancialCartDesc>{description}</FinancialCartDesc>}
|
||||||
</FinancialSectionCardContent>
|
{!isUndefined(progressValue) && (
|
||||||
|
<FinancialProgressBar intent={Intent.NONE} value={progressValue} />
|
||||||
|
)}
|
||||||
</FinancialSectionCard>
|
</FinancialSectionCard>
|
||||||
</React.Fragment>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export const FinancialDescription = ({ childern }) => {
|
|
||||||
return <FinancialCardText>{childern}</FinancialCardText>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FinancialProgressBar = ({ ...rest }) => {
|
export const FinancialProgressBar = ({ ...rest }) => {
|
||||||
return <FinancialCardProgressBar animate={false} stripes={false} {...rest} />;
|
return <FinancialCardProgressBar animate={false} stripes={false} {...rest} />;
|
||||||
@@ -41,34 +50,30 @@ const FinancialSectionCard = styled.div`
|
|||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
width: 230px;
|
width: 230px;
|
||||||
height: 116px;
|
height: 116px;
|
||||||
|
padding: 16px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
border: 1px solid #c8cad0;
|
||||||
|
gap: 6px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FinancialSectionCardContent = styled.div`
|
|
||||||
margin: 16px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const FinancialCardWrap = styled.div``;
|
|
||||||
|
|
||||||
const FinancialCardTitle = styled.div`
|
const FinancialCardTitle = styled.div`
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
color: #000;
|
color: #203252;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.5rem;
|
|
||||||
`;
|
`;
|
||||||
const FinancialCardValue = styled.div`
|
const FinancialCardValue = styled.div`
|
||||||
font-size: 21px;
|
font-size: 20px;
|
||||||
line-height: 2rem;
|
font-weight: 600;
|
||||||
font-weight: 700;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FinancialCardStatus = styled.div``;
|
const FinancialCartDesc = styled.div`
|
||||||
|
font-size: 12px;
|
||||||
|
`;
|
||||||
|
|
||||||
export const FinancialCardText = styled.div`
|
export const FinancialCardText = styled.div`
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.5rem;
|
color: #7b8195;
|
||||||
`;
|
`;
|
||||||
export const FinancialCardProgressBar = styled(ProgressBar)`
|
export const FinancialCardProgressBar = styled(ProgressBar)`
|
||||||
&.bp3-progress-bar {
|
&.bp3-progress-bar {
|
||||||
|
|||||||
@@ -6,9 +6,6 @@ const Schema = Yup.object().shape({
|
|||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.label(intl.get('project_task.schema.label.task_name'))
|
.label(intl.get('project_task.schema.label.task_name'))
|
||||||
.required(),
|
.required(),
|
||||||
charge_type: Yup.string()
|
|
||||||
.label(intl.get('project_task.schema.label.charge_type'))
|
|
||||||
.required(),
|
|
||||||
rate: Yup.number()
|
rate: Yup.number()
|
||||||
.label(intl.get('project_task.schema.label.rate'))
|
.label(intl.get('project_task.schema.label.rate'))
|
||||||
.required(),
|
.required(),
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import withDialogActions from '@/containers/Dialog/withDialogActions';
|
|||||||
|
|
||||||
const defaultInitialValues = {
|
const defaultInitialValues = {
|
||||||
name: '',
|
name: '',
|
||||||
charge_type: 'TIME',
|
|
||||||
estimate_hours: '',
|
estimate_hours: '',
|
||||||
rate: '0.00',
|
rate: '0.00',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,10 +8,9 @@ import {
|
|||||||
Col,
|
Col,
|
||||||
Row,
|
Row,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
|
InputPrependText,
|
||||||
} from '@/components';
|
} from '@/components';
|
||||||
import { EstimateAmount } from './utils';
|
import { EstimateAmount } from './utils';
|
||||||
import { taskChargeOptions } from '../common/modalChargeOptions';
|
|
||||||
import { ProjectTaskChargeTypeSelect } from '../../components';
|
|
||||||
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
|
||||||
import { compose } from '@/utils';
|
import { compose } from '@/utils';
|
||||||
|
|
||||||
@@ -48,17 +47,12 @@ function ProjectTaskFormFields({
|
|||||||
{/*------------ Charge -----------*/}
|
{/*------------ Charge -----------*/}
|
||||||
<Col xs={8}>
|
<Col xs={8}>
|
||||||
<FFormGroup
|
<FFormGroup
|
||||||
name={'charge_type'}
|
name={'rate'}
|
||||||
className={'form-group--select-list'}
|
className={'form-group--select-list'}
|
||||||
label={<T id={'project_task.dialog.charge'} />}
|
label={<T id={'project_task.dialog.charge'} />}
|
||||||
>
|
>
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<ProjectTaskChargeTypeSelect
|
<InputPrependText text={'Hourly Price'} />
|
||||||
name="charge_type"
|
|
||||||
items={taskChargeOptions}
|
|
||||||
popoverProps={{ minimal: true }}
|
|
||||||
filterable={false}
|
|
||||||
/>
|
|
||||||
<FInputGroup
|
<FInputGroup
|
||||||
name="rate"
|
name="rate"
|
||||||
disabled={values?.charge_type === 'non_chargable'}
|
disabled={values?.charge_type === 'non_chargable'}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ function ProjectTaskFormFloatingActions({
|
|||||||
<Button
|
<Button
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
style={{ minWidth: '75px' }}
|
style={{ minWidth: '85px' }}
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
{<T id={'save'} />}
|
{<T id={'save'} />}
|
||||||
|
|||||||
@@ -14,26 +14,12 @@ export function EstimateAmount({ baseCurrency }) {
|
|||||||
return (
|
return (
|
||||||
<EstimatedAmountBase>
|
<EstimatedAmountBase>
|
||||||
<EstimatedAmountContent>
|
<EstimatedAmountContent>
|
||||||
<Choose>
|
<EstimatedText>
|
||||||
<Choose.When condition={values?.charge_type === 'TIME'}>
|
|
||||||
<T id={'project_task.dialog.estimated_amount'} />
|
<T id={'project_task.dialog.estimated_amount'} />
|
||||||
|
</EstimatedText>
|
||||||
<EstimatedAmount>
|
<EstimatedAmount>
|
||||||
<Money amount={estimatedAmount} currency={baseCurrency} />
|
<Money amount={estimatedAmount} currency={baseCurrency} />
|
||||||
</EstimatedAmount>
|
</EstimatedAmount>
|
||||||
</Choose.When>
|
|
||||||
<Choose.When condition={values?.charge_type === 'FIXED'}>
|
|
||||||
<T id={'project_task.dialog.total'} />
|
|
||||||
<EstimatedAmount>
|
|
||||||
<Money amount={values.rate} currency={baseCurrency} />
|
|
||||||
</EstimatedAmount>
|
|
||||||
</Choose.When>
|
|
||||||
<Choose.Otherwise>
|
|
||||||
<T id={'project_task.dialog.total'} />
|
|
||||||
<EstimatedAmount>
|
|
||||||
<Money amount={0.0} currency={baseCurrency} />
|
|
||||||
</EstimatedAmount>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
</EstimatedAmountContent>
|
</EstimatedAmountContent>
|
||||||
</EstimatedAmountBase>
|
</EstimatedAmountBase>
|
||||||
);
|
);
|
||||||
@@ -42,9 +28,7 @@ export function EstimateAmount({ baseCurrency }) {
|
|||||||
const EstimatedAmountBase = styled.div`
|
const EstimatedAmountBase = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
font-size: 14px;
|
margin-bottom: 2rem;
|
||||||
line-height: 1.5rem;
|
|
||||||
opacity: 0.75;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const EstimatedAmountContent = styled.span`
|
const EstimatedAmountContent = styled.span`
|
||||||
@@ -53,7 +37,11 @@ const EstimatedAmountContent = styled.span`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const EstimatedAmount = styled.span`
|
const EstimatedAmount = styled.span`
|
||||||
font-size: 15px;
|
font-size: 18px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const EstimatedText = styled.span`
|
||||||
|
color: #607090;
|
||||||
|
`;
|
||||||
|
|||||||
@@ -10,9 +10,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
Icon,
|
Icon,
|
||||||
Can,
|
Can,
|
||||||
AdvancedFilterPopover,
|
|
||||||
DashboardActionViewsList,
|
DashboardActionViewsList,
|
||||||
DashboardFilterButton,
|
|
||||||
DashboardRowsHeightButton,
|
DashboardRowsHeightButton,
|
||||||
FormattedMessage as T,
|
FormattedMessage as T,
|
||||||
DashboardActionsBar,
|
DashboardActionsBar,
|
||||||
@@ -86,8 +84,6 @@ function ProjectsActionsBar({
|
|||||||
onClick={handleNewProjectBtnClick}
|
onClick={handleNewProjectBtnClick}
|
||||||
/>
|
/>
|
||||||
</Can>
|
</Can>
|
||||||
{/* AdvancedFilterPopover */}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
icon={<Icon icon={'print-16'} iconSize={'16'} />}
|
icon={<Icon icon={'print-16'} iconSize={'16'} />}
|
||||||
|
|||||||
@@ -66,14 +66,12 @@ function ProjectsDataTable({
|
|||||||
action: 'edit',
|
action: 'edit',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle new task button click.
|
// Handle new task button click.
|
||||||
const handleNewTaskButtonClick = (project) => {
|
const handleNewTaskButtonClick = (project) => {
|
||||||
openDialog('project-task-form', {
|
openDialog('project-task-form', {
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Local storage memorizing columns widths.
|
// Local storage memorizing columns widths.
|
||||||
const [initialColumnsWidths, , handleColumnResizing] =
|
const [initialColumnsWidths, , handleColumnResizing] =
|
||||||
useMemorizedColumnsWidths(TABLES.PROJECTS);
|
useMemorizedColumnsWidths(TABLES.PROJECTS);
|
||||||
@@ -132,32 +130,7 @@ export default compose(
|
|||||||
const ProjectsTable = styled(DataTable)`
|
const ProjectsTable = styled(DataTable)`
|
||||||
.tbody {
|
.tbody {
|
||||||
.tr .td {
|
.tr .td {
|
||||||
padding: 0.5rem 0.8rem;
|
padding: 0.75rem 0.8rem;
|
||||||
}
|
|
||||||
.avatar.td {
|
|
||||||
.cell-inner {
|
|
||||||
.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 {
|
.table-size--small {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function ProjectsList({
|
|||||||
<ProjectsActionsBar />
|
<ProjectsActionsBar />
|
||||||
<DashboardPageContent>
|
<DashboardPageContent>
|
||||||
<ProjectsViewTabs />
|
<ProjectsViewTabs />
|
||||||
|
|
||||||
<DashboardContentTable>
|
<DashboardContentTable>
|
||||||
<ProjectsDataTable />
|
<ProjectsDataTable />
|
||||||
</DashboardContentTable>
|
</DashboardContentTable>
|
||||||
|
|||||||
@@ -28,12 +28,12 @@ import { safeCallback, firstLettersArgs, calculateStatus } from '@/utils';
|
|||||||
export function ProjectStatus({ row }) {
|
export function ProjectStatus({ row }) {
|
||||||
return (
|
return (
|
||||||
<ProjectStatusRoot>
|
<ProjectStatusRoot>
|
||||||
<ProjectStatusTaskAmount>{row.cost_estimate}</ProjectStatusTaskAmount>
|
<ProjectStatusTaskAmount>{row.total_expenses_formatted}</ProjectStatusTaskAmount>
|
||||||
<ProjectProgressBar
|
<ProjectProgressBar
|
||||||
animate={false}
|
animate={false}
|
||||||
stripes={false}
|
stripes={false}
|
||||||
intent={Intent.PRIMARY}
|
intent={Intent.PRIMARY}
|
||||||
value={calculateStatus(100, row.cost_estimate)}
|
value={calculateStatus(row.total_expenses, row.cost_estimate)}
|
||||||
/>
|
/>
|
||||||
</ProjectStatusRoot>
|
</ProjectStatusRoot>
|
||||||
);
|
);
|
||||||
@@ -44,37 +44,10 @@ export function ProjectStatus({ row }) {
|
|||||||
*/
|
*/
|
||||||
export const StatusAccessor = (row) => {
|
export const StatusAccessor = (row) => {
|
||||||
return (
|
return (
|
||||||
<Choose>
|
|
||||||
<Choose.When condition={row.status_formatted === 'InProgress'}>
|
|
||||||
<ProjectStatus row={row} />
|
<ProjectStatus row={row} />
|
||||||
</Choose.When>
|
|
||||||
<Choose.When condition={row.status_formatted === 'Closed'}>
|
|
||||||
<StatusTagWrap>
|
|
||||||
<Tag minimal={true} intent={Intent.SUCCESS} round={true}>
|
|
||||||
{row.status_formatted}
|
|
||||||
</Tag>
|
|
||||||
</StatusTagWrap>
|
|
||||||
</Choose.When>
|
|
||||||
<Choose.Otherwise>
|
|
||||||
<StatusTagWrap>
|
|
||||||
<Tag minimal={true} round={true}>
|
|
||||||
<T id={'draft'} />
|
|
||||||
</Tag>
|
|
||||||
</StatusTagWrap>
|
|
||||||
</Choose.Otherwise>
|
|
||||||
</Choose>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Avatar cell.
|
|
||||||
*/
|
|
||||||
export const AvatarCell = ({ row: { original }, size }) => (
|
|
||||||
<span className="avatar" data-size={size}>
|
|
||||||
{firstLettersArgs(original?.contact_display_name, original?.name)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Table actions cell.
|
* Table actions cell.
|
||||||
*/
|
*/
|
||||||
@@ -135,6 +108,11 @@ export const ActionsMenu = ({
|
|||||||
* Projects accessor.
|
* Projects accessor.
|
||||||
*/
|
*/
|
||||||
export const ProjectsAccessor = (row) => (
|
export const ProjectsAccessor = (row) => (
|
||||||
|
<ProjectName>
|
||||||
|
<ProjectAvatar data-size="medium">
|
||||||
|
{firstLettersArgs(row?.contact_display_name, row?.name)}
|
||||||
|
</ProjectAvatar>
|
||||||
|
|
||||||
<ProjectItemsWrap>
|
<ProjectItemsWrap>
|
||||||
<ProjectItemsHeader>
|
<ProjectItemsHeader>
|
||||||
<ProjectItemContactName>
|
<ProjectItemContactName>
|
||||||
@@ -142,6 +120,7 @@ export const ProjectsAccessor = (row) => (
|
|||||||
</ProjectItemContactName>
|
</ProjectItemContactName>
|
||||||
<ProjectItemProjectName>{row.name}</ProjectItemProjectName>
|
<ProjectItemProjectName>{row.name}</ProjectItemProjectName>
|
||||||
</ProjectItemsHeader>
|
</ProjectItemsHeader>
|
||||||
|
|
||||||
<ProjectItemDescription>
|
<ProjectItemDescription>
|
||||||
<FormatDate value={row.deadline_formatted} />
|
<FormatDate value={row.deadline_formatted} />
|
||||||
{intl.get('projects.label.cost_estimate', {
|
{intl.get('projects.label.cost_estimate', {
|
||||||
@@ -149,6 +128,7 @@ export const ProjectsAccessor = (row) => (
|
|||||||
})}
|
})}
|
||||||
</ProjectItemDescription>
|
</ProjectItemDescription>
|
||||||
</ProjectItemsWrap>
|
</ProjectItemsWrap>
|
||||||
|
</ProjectName>
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -157,16 +137,6 @@ export const ProjectsAccessor = (row) => (
|
|||||||
export const useProjectsListColumns = () => {
|
export const useProjectsListColumns = () => {
|
||||||
return React.useMemo(
|
return React.useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
|
||||||
id: 'avatar',
|
|
||||||
Header: '',
|
|
||||||
Cell: AvatarCell,
|
|
||||||
className: 'avatar',
|
|
||||||
width: 45,
|
|
||||||
disableResizing: true,
|
|
||||||
disableSortBy: true,
|
|
||||||
clickable: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'name',
|
id: 'name',
|
||||||
Header: '',
|
Header: '',
|
||||||
@@ -196,16 +166,18 @@ const ProjectItemsHeader = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const ProjectItemContactName = styled.div`
|
const ProjectItemContactName = styled.div`
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
padding-right: 4px;
|
padding-right: 10px;
|
||||||
|
`;
|
||||||
|
const ProjectItemProjectName = styled.div`
|
||||||
|
color: #595b66;
|
||||||
`;
|
`;
|
||||||
const ProjectItemProjectName = styled.div``;
|
|
||||||
|
|
||||||
const ProjectItemDescription = styled.div`
|
const ProjectItemDescription = styled.div`
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
margin-top: 0.2rem;
|
margin-top: 0.3rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -218,8 +190,7 @@ const ProjectStatusRoot = styled.div`
|
|||||||
|
|
||||||
const ProjectStatusTaskAmount = styled.div`
|
const ProjectStatusTaskAmount = styled.div`
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-weight: 400;
|
font-size: 15px;
|
||||||
line-height: 1.5rem;
|
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -244,3 +215,36 @@ const StatusTagWrap = styled.div`
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const ProjectName = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 15px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Avatar = styled.div`
|
||||||
|
display: inline-block;
|
||||||
|
background: #adbcc9;
|
||||||
|
border-radius: 8%;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
&[data-size='medium'] {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
&[data-size='small'] {
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
line-height: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ProjectAvatar = styled(Avatar)`
|
||||||
|
margin-top: auto;
|
||||||
|
margin-bottom: auto;
|
||||||
|
`;
|
||||||
Reference in New Issue
Block a user