feat: project detail.

This commit is contained in:
elforjani13
2022-09-25 22:57:53 +02:00
parent 3c3f8c6731
commit 6363576c5e
13 changed files with 192 additions and 38 deletions

View File

@@ -31,7 +31,7 @@ const chargeTypeSelectProps = {
* @param param0
* @returns
*/
export function ChangeTypesSelect({ items, ...rest }) {
export function ProjectTaskChargeTypeSelect({ items, ...rest }) {
return (
<FSelect
{...chargeTypeSelectProps}

View File

@@ -48,7 +48,12 @@ const taskSelectProps = {
labelAccessor: 'name',
};
export function TaskSelect({ tasks, ...rest }) {
/**
*
* @param param0
* @returns
*/
export function ProjectTaskSelect({ tasks, ...rest }) {
return (
<FSelect
items={tasks}

View File

@@ -13,7 +13,7 @@ import {
import {
ExpenseSelect,
FInputGroupComponent,
ChangeTypesSelect,
ProjectTaskChargeTypeSelect,
} from '../../components';
import { useEstimatedExpenseFormContext } from './EstimatedExpenseFormProvider';
import EstimatedExpenseFormChargeFields from './EstimatedExpenseFormChargeFields';
@@ -75,7 +75,7 @@ export default function EstimatedExpenseFormFields() {
label={<T id={'estimated_expenses.dialog.charge'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ChangeTypesSelect
<ProjectTaskChargeTypeSelect
name="charge"
items={expenseChargeOption}
popoverProps={{ minimal: true }}

View File

@@ -1,6 +1,7 @@
// @ts-nocheck
import React from 'react';
import { DashboardInsider } from '@/components';
import { useProject } from '../../hooks';
const ProjectDetailContext = React.createContext();
@@ -13,12 +14,18 @@ function ProjectDetailProvider({
// #ownProps
...props
}) {
// Handle fetch project detail.
const { data: project, isLoading: isProjectLoading } = useProject(projectId, {
enabled: !!projectId,
});
// State provider.
const provider = {
project,
projectId,
};
return (
<DashboardInsider className="timesheets">
<DashboardInsider loading={isProjectLoading}>
<ProjectDetailContext.Provider value={provider} {...props} />
</DashboardInsider>
);

View File

@@ -0,0 +1,75 @@
// @ts-nocheck
import React from 'react';
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 {
DetailFinancialCard,
DetailFinancialSection,
FinancialProgressBar,
FinancialCardText,
} from './components';
import { calculateStatus } from '@/utils';
import { useCalculateProject } from './utils';
import { useProjectDetailContext } from './ProjectDetailProvider';
/**
* Project details header.
* @returns
*/
export function ProjectDetailHeader() {
const { project } = useProjectDetailContext();
const { percentageOfInvoice, percentageOfExpense } = useCalculateProject();
return (
<DetailFinancialSection>
<DetailFinancialCard
label={intl.get('project_details.label.project_estimate')}
value={project.cost_estimate_formatted}
/>
<DetailFinancialCard
label={intl.get('project_details.label.invoiced')}
value={project.total_invoiced_formatted}
>
<FinancialCardText>
{intl.get('project_details.label.of_project_estimate', {
value: percentageOfInvoice,
})}
</FinancialCardText>
<FinancialProgressBar
intent={Intent.NONE}
value={calculateStatus(project.total_invoiced, project.cost_estimate)}
/>
</DetailFinancialCard>
<DetailFinancialCard
label={intl.get('project_details.label.time_expenses')}
value={project.total_expenses_formatted}
>
<FinancialCardText>
{intl.get('project_details.label.of_project_estimate', {
value: percentageOfExpense,
})}
</FinancialCardText>
<FinancialProgressBar
intent={Intent.NONE}
value={calculateStatus(project.total_expenses, project.cost_estimate)}
/>
</DetailFinancialCard>
<DetailFinancialCard
label={intl.get('project_details.label.to_be_invoiced')}
value={project.total_billable_formatted}
/>
<DetailFinancialCard
label={'Deadline'}
value={<FormatDate value={project.deadline_formatted} />}
>
<FinancialCardText>4 days to go</FinancialCardText>
</DetailFinancialCard>
</DetailFinancialSection>
);
}

View File

@@ -2,7 +2,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import styled from 'styled-components';
import { Icon } from '@/components';
import { Icon, If, Choose, FormattedMessage as T } from '@/components';
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
import { safeCallback } from '@/utils';
@@ -30,22 +30,39 @@ export function ActionsMenu({
);
}
export function TaskAccessor(row) {
/**
*
* @returns
*/
function TaskChrageType({ values: { charge_type, rate } }) {
return (
<Choose>
<Choose.When condition={charge_type === 'TIME'}>
<T id={'project_task.rate'} values={{ rate: rate }} />
</Choose.When>
<Choose.When condition={charge_type === 'FIXED'}>
<T id={'project_task.fixed_price'} />
</Choose.When>
<Choose.When condition={charge_type === 'NON_CHARGABLE'}>
<T id={'project_task.non_chargable'} />
</Choose.When>
</Choose>
);
}
export function TaskAccessor(task) {
return (
<TaskRoot>
<TaskHeader>
<TaskTitle>{row.name}</TaskTitle>
<TaskTitle>{task.name}</TaskTitle>
</TaskHeader>
<TaskContent>
{row.charge_type === 'TIME'
? intl.get('project_task.rate', {
rate: row.rate,
})
: row.charge_type}
<TaskChrageType values={task} />
<TaskDescription>
{row.estimate_minutes &&
intl.get('project_task.estimate_minutes', {
estimate_minutes: row.estimate_minutes,
{task.estimate_hours &&
intl.get('project_task.estimate_hours', {
estimate_hours: task.estimate_hours,
})}
</TaskDescription>
</TaskContent>

View File

@@ -2,14 +2,14 @@
import React from 'react';
import styled from 'styled-components';
import { ProjectTasksHeader } from './ProjectTasksHeader';
import { ProjectDetailHeader } from '../ProjectDetailsHeader';
import { ProjectTasksTable } from './ProjectTasksTable';
import { ProjectTaskProvider } from './ProjectTaskProvider';
export default function ProjectTasks() {
return (
<ProjectTaskProvider>
<ProjectTasksHeader />
<ProjectDetailHeader />
<ProjectTasksTableCard>
<ProjectTasksTable />
</ProjectTasksTableCard>

View File

@@ -2,8 +2,8 @@
import React from 'react';
import styled from 'styled-components';
import { ProjectDetailHeader } from '../ProjectDetailsHeader';
import { ProjectTimesheetsTable } from './ProjectTimesheetsTable';
import { ProjectTimesheetsHeader } from './ProjectTimesheetsHeader';
import { ProjectTimesheetsProvider } from './ProjectTimesheetsProvider';
/**
@@ -13,7 +13,7 @@ import { ProjectTimesheetsProvider } from './ProjectTimesheetsProvider';
export default function ProjectTimeSheets() {
return (
<ProjectTimesheetsProvider>
<ProjectTimesheetsHeader />
<ProjectDetailHeader />
<ProjectTimesheetTableCard>
<ProjectTimesheetsTable />
</ProjectTimesheetTableCard>

View File

@@ -0,0 +1,30 @@
//@ts-nocheck
import React from 'react';
import moment from 'moment';
import { subtract } from 'lodash';
import { calculateStatus } from '@/utils';
import { useProjectDetailContext } from './ProjectDetailProvider';
function calculateProject(costEstiate, totalAmount) {
return (costEstiate / totalAmount) * 100;
}
export const useCalculateProject = () => {
const { project } = useProjectDetailContext();
const percentageOfInvoice = calculateProject(
project?.total_invoiced,
project?.cost_estimate,
);
const percentageOfExpense = calculateProject(
project?.total_expenses,
project?.cost_estimate,
);
return {
percentageOfInvoice,
percentageOfExpense,
};
};

View File

@@ -14,7 +14,7 @@ import {
import {
ExpenseSelect,
FInputGroupComponent,
ChangeTypesSelect,
ProjectTaskChargeTypeSelect,
} from '../../components';
import ExpenseFormChargeFields from './ProjectExpenseFormChargeFields';
import { momentFormatter } from '@/utils';
@@ -100,7 +100,7 @@ export default function ProjectExpenseFormFields() {
label={<T id={'project_expense.dialog.charge'} />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<ChangeTypesSelect
<ProjectTaskChargeTypeSelect
name="expenseCharge"
items={expenseChargeOption}
popoverProps={{ minimal: true }}

View File

@@ -16,8 +16,8 @@ import {
} from '@/components';
import { useProjectTimeEntryFormContext } from './ProjectTimeEntryFormProvider';
import {
TaskSelect,
ProjectsSelect,
ProjectTaskSelect,
ProjectSelectButton,
} from '../../components';
import { momentFormatter } from '@/utils';
@@ -59,7 +59,7 @@ function ProjectTimeEntryFormFields() {
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--select-list', Classes.FILL)}
>
<TaskSelect
<ProjectTaskSelect
name={'task_id'}
tasks={projectTasks}
popoverProps={{ minimal: true }}

View File

@@ -49,14 +49,18 @@ export const StatusAccessor = (row) => {
<ProjectStatus row={row} />
</Choose.When>
<Choose.When condition={row.status_formatted === 'Closed'}>
<StatusTag minimal={true} intent={Intent.SUCCESS} round={true}>
{row.status_formatted}
</StatusTag>
<StatusTagWrap>
<Tag minimal={true} intent={Intent.SUCCESS} round={true}>
{row.status_formatted}
</Tag>
</StatusTagWrap>
</Choose.When>
<Choose.Otherwise>
<StatusTag minimal={true} round={true}>
<T id={'draft'} />
</StatusTag>
<StatusTagWrap>
<Tag minimal={true} round={true}>
<T id={'draft'} />
</Tag>
</StatusTagWrap>
</Choose.Otherwise>
</Choose>
);
@@ -167,7 +171,7 @@ export const useProjectsListColumns = () => {
id: 'name',
Header: '',
accessor: ProjectsAccessor,
width: 240,
width: 140,
className: 'name',
clickable: true,
},
@@ -232,7 +236,11 @@ const ProjectProgressBar = styled(ProgressBar)`
}
`;
const StatusTag = styled(Tag)`
min-width: 65px;
text-align: center;
const StatusTagWrap = styled.div`
display: flex;
justify-content: center;
.tag {
min-width: 65px;
text-align: center;
}
`;

View File

@@ -2083,8 +2083,10 @@
"project_task.dialog.edit_success_message": "The task has been edited successfully.",
"project_task.action.edit_task": "Edit Task",
"project_task.action.delete_task": "Delete Task",
"project_task.rate": "{rate} /hour",
"project_task.estimate_minutes": "• {estimate_minutes}h 0m estimated",
"project_task.rate": "{rate} / hour",
"project_task.fixed_price": "Fixed price",
"project_task.non_chargable": "Non-chargeable",
"project_task.estimate_hours": "• {estimate_hours}h 0m estimated",
"project_task.alert.delete_message": "The deleted task has been deleted successfully.",
"project_task.alert.once_delete_this_project": "Once you delete this task, you won't be able to restore it later. Are you sure you want to delete this task?",
"fixed_price": "Fixed price",
@@ -2095,7 +2097,7 @@
"project.schema.label.project_state": "Project state",
"project.schema.label.project_cost": "Project cost",
"project_task.schema.label.task_name": "Task name",
"project_task.schema.label.task_house": "Task house",
"project_task.schema.label.estimate_hours": "Estimate hours",
"project_task.schema.label.charge_type": "Charge type",
"project_task.schema.label.rate": "Rate",
"project_task.schema.label.amount": "Amount",
@@ -2112,6 +2114,11 @@
"project_details.new_invoicing": "New Invoicing",
"project_details.new_expense": "New Expense",
"project_details.new_estimated_expense": "New Estimated Expense",
"project_details.label.project_estimate": "Project estimate",
"project_details.label.invoiced": "Invoiced",
"project_details.label.time_expenses": "Time & Expenses",
"project_details.label.to_be_invoiced": "To be invoiced",
"project_details.label.of_project_estimate": "{value}% of project estimate",
"timesheets.action.delete_timesheet": "Delete",
"timesheets.action.edit_timesheet": "Edit Timesheet",
"timesheets.column.date": "Date",
@@ -2215,5 +2222,10 @@
"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"
"project_billable_entries.dialog.bill": "Bill",
"project_billable_entries.dialog.add": "Add",
"project_billable_entries.dialog.show": "Show",
"project_billable_entries.alert.there_is_no_billable_entries": "There is no billable entries for that project.",
"project_billable_entries.billable_type": "Billable {value}",
"add_billable_entries": "Add Billable Entries"
}