mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-14 11:50:31 +00:00
feat: add project timesheet.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
@@ -15,7 +16,7 @@ import {
|
||||
import withSettings from '../../../Settings/withSettings';
|
||||
import withSettingsActions from '../../../Settings/withSettingsActions';
|
||||
import withDialogActions from 'containers/Dialog/withDialogActions';
|
||||
|
||||
import { useProjectDetailContext } from './ProjectDetailProvider';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
@@ -32,16 +33,25 @@ function ProjectDetailActionsBar({
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
const { projectId } = useProjectDetailContext();
|
||||
|
||||
// Handle new transaction button click.
|
||||
const handleNewTransactionBtnClick = () => {};
|
||||
|
||||
const handleEditProjectBtnClick = () => {
|
||||
openDialog('project-form', {
|
||||
projectId,
|
||||
});
|
||||
};
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('timesheets', 'tableSize', size);
|
||||
};
|
||||
|
||||
const handleTimeEntryBtnClick = () => {
|
||||
openDialog('time-entry-form');
|
||||
openDialog('time-entry-form', {
|
||||
projectId,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle the refresh button click.
|
||||
@@ -58,14 +68,15 @@ function ProjectDetailActionsBar({
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={<T id={'projcet_details.action.log_time'} />}
|
||||
icon={<Icon icon={'time-24'} iconSize={16} />}
|
||||
text={<T id={'projcet_details.action.time_entry'} />}
|
||||
onClick={handleTimeEntryBtnClick}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="pen-18" />}
|
||||
text={<T id={'projcet_details.action.edit_project'} />}
|
||||
// onClick={}
|
||||
onClick={handleEditProjectBtnClick}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
|
||||
@@ -9,11 +9,14 @@ const ProjectDetailContext = React.createContext();
|
||||
* @returns
|
||||
*/
|
||||
function ProjectDetailProvider({
|
||||
projectId,
|
||||
// #ownProps
|
||||
...props
|
||||
}) {
|
||||
// State provider.
|
||||
const provider = {};
|
||||
const provider = {
|
||||
projectId,
|
||||
};
|
||||
return (
|
||||
<DashboardInsider class="timesheets">
|
||||
<ProjectDetailContext.Provider value={provider} {...props} />
|
||||
|
||||
@@ -2,7 +2,8 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import TimesheetDataTable from './TimesheetDataTable';
|
||||
|
||||
import ProjectTimesheet from './ProjectTimesheet';
|
||||
|
||||
/**
|
||||
* Project detail tabs.
|
||||
@@ -21,7 +22,7 @@ export default function ProjectDetailTabs() {
|
||||
<Tab
|
||||
id="timesheet"
|
||||
title={intl.get('project_details.label.timesheet')}
|
||||
panel={<TimesheetDataTable />}
|
||||
panel={<ProjectTimesheet />}
|
||||
/>
|
||||
<Tab
|
||||
id="purchases"
|
||||
@@ -58,11 +59,9 @@ const ProjectTabsContent = styled.div`
|
||||
}
|
||||
}
|
||||
.bp3-tab-panel {
|
||||
border: 2px solid #f0f0f0;
|
||||
border-radius: 10px;
|
||||
padding: 30px 18px;
|
||||
margin: 30px 15px;
|
||||
background: #fff;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
padding: 0 25px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Intent, ProgressBar } from '@blueprintjs/core';
|
||||
import { FormatDate } from 'components';
|
||||
|
||||
import { calculateStatus } from 'utils';
|
||||
|
||||
|
||||
/**
|
||||
* Project Financial Section.
|
||||
* @returns
|
||||
*/
|
||||
export default function ProjectFinancialSection() {
|
||||
return (
|
||||
<ProjectFinancialSectionRoot>
|
||||
<FinancialSectionCard>
|
||||
<FinancialSectionContent>
|
||||
<FinancialSectionTitle>Project estimate</FinancialSectionTitle>
|
||||
<FinancialSectionValue>3.14</FinancialSectionValue>
|
||||
</FinancialSectionContent>
|
||||
</FinancialSectionCard>
|
||||
|
||||
<FinancialSectionCard>
|
||||
<FinancialSectionContent>
|
||||
<FinancialSectionTitle>Invoiced</FinancialSectionTitle>
|
||||
<FinancialSectionValue>0.00</FinancialSectionValue>
|
||||
<FinancialSectionStatus>
|
||||
<FinancialSectionText>0% of project estimate</FinancialSectionText>
|
||||
<FinancialSectionProgressBar
|
||||
animate={false}
|
||||
intent={Intent.NONE}
|
||||
value={0}
|
||||
/>
|
||||
</FinancialSectionStatus>
|
||||
</FinancialSectionContent>
|
||||
</FinancialSectionCard>
|
||||
|
||||
<FinancialSectionCard>
|
||||
<FinancialSectionContent>
|
||||
<FinancialSectionTitle>Time & Expenses</FinancialSectionTitle>
|
||||
<FinancialSectionValue>0.00</FinancialSectionValue>
|
||||
<FinancialSectionStatus>
|
||||
<FinancialSectionText>0% of project estimate</FinancialSectionText>
|
||||
<FinancialSectionProgressBar
|
||||
animate={false}
|
||||
intent={Intent.NONE}
|
||||
value={0}
|
||||
/>
|
||||
</FinancialSectionStatus>
|
||||
</FinancialSectionContent>
|
||||
</FinancialSectionCard>
|
||||
|
||||
<FinancialSectionCard>
|
||||
<FinancialSectionContent>
|
||||
<FinancialSectionTitle>To be invoiced</FinancialSectionTitle>
|
||||
<FinancialSectionValue>3.14</FinancialSectionValue>
|
||||
</FinancialSectionContent>
|
||||
</FinancialSectionCard>
|
||||
|
||||
<FinancialSectionCard>
|
||||
<FinancialSectionContent>
|
||||
<FinancialSectionTitle>Deadline</FinancialSectionTitle>
|
||||
<FinancialSectionValue>
|
||||
<FormatDate value={'2022-06-08T22:00:00.000Z'} />
|
||||
</FinancialSectionValue>
|
||||
<FinancialSectionText>4 days to go</FinancialSectionText>
|
||||
</FinancialSectionContent>
|
||||
</FinancialSectionCard>
|
||||
</ProjectFinancialSectionRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export const ProjectFinancialSectionRoot = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 20px 20px 20px;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
export const FinancialSectionCard = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
border-radius: 3px;
|
||||
width: 220px;
|
||||
height: 116px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
||||
`;
|
||||
|
||||
export const FinancialSectionContent = styled.div`
|
||||
margin: 16px;
|
||||
/* flex-direction: column; */
|
||||
`;
|
||||
|
||||
export const FinancialSectionTitle = styled.div`
|
||||
font-size: 15px;
|
||||
color: #000;
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
export const FinancialSectionValue = styled.div`
|
||||
font-size: 21px;
|
||||
line-height: 2rem;
|
||||
font-weight: 700;
|
||||
`;
|
||||
|
||||
export const FinancialSectionStatus = styled.div``;
|
||||
|
||||
export const FinancialSectionText = styled.div`
|
||||
font-size: 13px;
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
export const FinancialSectionProgressBar = styled(ProgressBar)`
|
||||
&.bp3-progress-bar {
|
||||
height: 3px;
|
||||
&,
|
||||
.bp3-progress-meter {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,124 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DataTable } from 'components';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
import { useTimesheetColumns, ActionsMenu } from './components';
|
||||
import { TABLES } from 'common/tables';
|
||||
import { useMemorizedColumnsWidths } from 'hooks';
|
||||
import withSettings from '../../../../Settings/withSettings';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const Timesheet = [
|
||||
{
|
||||
id: 1,
|
||||
date: '2022-06-08T22:00:00.000Z',
|
||||
name: 'Lighting',
|
||||
display_name: 'Kyrie Rearden',
|
||||
description: 'Laid paving stones',
|
||||
duration: '12:00',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2022-06-08T22:00:00.000Z',
|
||||
name: 'Interior Decoration',
|
||||
display_name: 'Project Sherwood',
|
||||
description: 'Laid paving stones',
|
||||
duration: '11:00',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Timesheet DataTable.
|
||||
* @returns
|
||||
*/
|
||||
function TimesheetsTable({
|
||||
// #withSettings
|
||||
timesheetsTableSize,
|
||||
}) {
|
||||
// Retrieve timesheet table columns.
|
||||
const columns = useTimesheetColumns();
|
||||
|
||||
// Handle delete timesheet.
|
||||
const handleDeleteTimesheet = () => {};
|
||||
|
||||
// Local storage memorizing columns widths.
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.TIMESHEETS);
|
||||
|
||||
return (
|
||||
<TimesheetDataTable
|
||||
columns={columns}
|
||||
data={Timesheet}
|
||||
// loading={}
|
||||
// headerLoading={}
|
||||
// progressBarLoading={}
|
||||
manualSortBy={true}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
hideTableHeader={true}
|
||||
ContextMenu={ActionsMenu}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
initialColumnsWidths={initialColumnsWidths}
|
||||
onColumnResizing={handleColumnResizing}
|
||||
size={timesheetsTableSize}
|
||||
payload={{
|
||||
onDelete: handleDeleteTimesheet,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withSettings(({ timesheetsSettings }) => ({
|
||||
timesheetsTableSize: timesheetsSettings?.tableSize,
|
||||
})),
|
||||
)(TimesheetsTable);
|
||||
|
||||
const TimesheetDataTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead .tr .th {
|
||||
.resizer {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody {
|
||||
.tr .td {
|
||||
padding: 0.5rem 0.8rem;
|
||||
}
|
||||
|
||||
.avatar.td {
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
background: #adbcc9;
|
||||
border-radius: 50%;
|
||||
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,120 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import styled from 'styled-components';
|
||||
import { FormatDate, Icon, FormattedMessage as T } from 'components';
|
||||
import { Menu, MenuItem, Intent } from '@blueprintjs/core';
|
||||
import { safeCallback, firstLettersArgs } from 'utils';
|
||||
import { chain } from 'lodash';
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onDelete, onViewDetails },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={intl.get('timesheets.actions.delete_timesheet')}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Avatar cell.
|
||||
*/
|
||||
export const AvatarCell = ({ row: { original }, size }) => (
|
||||
<span className="avatar" data-size={size}>
|
||||
{firstLettersArgs(original?.display_name, original?.name)}
|
||||
</span>
|
||||
);
|
||||
|
||||
/**
|
||||
* Timesheet accessor.
|
||||
*/
|
||||
export const TimesheetAccessor = (timesheet) => (
|
||||
<React.Fragment>
|
||||
<TimesheetHeader>
|
||||
<TimesheetTitle>{timesheet.display_name}</TimesheetTitle>
|
||||
<TimesheetSubTitle>{timesheet.name}</TimesheetSubTitle>
|
||||
</TimesheetHeader>
|
||||
<TimesheetContent>
|
||||
<FormatDate value={timesheet.date} />
|
||||
<TimesheetDescription>{timesheet.description}</TimesheetDescription>
|
||||
</TimesheetContent>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
const TimesheetHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-flow: wrap;
|
||||
`;
|
||||
const TimesheetTitle = styled.span`
|
||||
font-weight: 500;
|
||||
margin-right: 12px;
|
||||
line-height: 1.5rem;
|
||||
`;
|
||||
|
||||
const TimesheetSubTitle = styled.span``;
|
||||
const TimesheetContent = styled.div`
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
font-size: 13px;
|
||||
opacity: 0.75;
|
||||
margin-bottom: 0.1rem;
|
||||
line-height: 1.2rem;
|
||||
`;
|
||||
|
||||
const TimesheetDescription = styled.span`
|
||||
&::before {
|
||||
content: '•';
|
||||
margin: 0.3rem;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Retrieve timesheet list columns columns.
|
||||
*/
|
||||
export function useTimesheetColumns() {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'avatar',
|
||||
Header: '',
|
||||
Cell: AvatarCell,
|
||||
className: 'avatar',
|
||||
width: 45,
|
||||
disableResizing: true,
|
||||
disableSortBy: true,
|
||||
clickable: true,
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Header',
|
||||
accessor: TimesheetAccessor,
|
||||
width: 100,
|
||||
className: 'name',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
Header: '',
|
||||
accessor: 'duration',
|
||||
width: 100,
|
||||
className: 'duration',
|
||||
align: 'right',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import TimesheetsTable from './TimesheetsTable';
|
||||
import ProjectFinancialSection from '../ProjectFinancialSection';
|
||||
|
||||
/**
|
||||
* Project Timesheet.
|
||||
* @returns
|
||||
*/
|
||||
export default function ProjectTimesheet() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ProjectFinancialSection />
|
||||
|
||||
<ProjectTimesheetTableCard>
|
||||
<TimesheetsTable />
|
||||
</ProjectTimesheetTableCard>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const ProjectTimesheetTableCard = styled.div`
|
||||
margin: 20px;
|
||||
border: 1px solid #c8cad0; // #000a1e33 #f0f0f0
|
||||
border-radius: 3px;
|
||||
background: #fff;
|
||||
`;
|
||||
@@ -1,83 +0,0 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormatDateCell, Icon } from 'components';
|
||||
import { Menu, MenuDivider, MenuItem, Intent } from '@blueprintjs/core';
|
||||
import { safeCallback } from 'utils';
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
|
||||
export function ActionsMenu({
|
||||
payload: { onDelete, onViewDetails },
|
||||
row: { original },
|
||||
}) {
|
||||
return (
|
||||
<Menu>
|
||||
<MenuItem
|
||||
text={'Delete'}
|
||||
intent={Intent.DANGER}
|
||||
onClick={safeCallback(onDelete, original)}
|
||||
icon={<Icon icon="trash-16" iconSize={16} />}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve timesheet list columns columns.
|
||||
*/
|
||||
export function useTimesheetColumns() {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: intl.get('timesheets.column.date'),
|
||||
accessor: 'date',
|
||||
Cell: FormatDateCell,
|
||||
width: 100,
|
||||
className: 'date',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'task',
|
||||
Header: intl.get('timesheets.column.task'),
|
||||
accessor: 'task',
|
||||
width: 100,
|
||||
className: 'task',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
Header: intl.get('timesheets.column.user'),
|
||||
accessor: 'user',
|
||||
width: 100,
|
||||
className: 'user',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'time',
|
||||
Header: intl.get('timesheets.column.time'),
|
||||
accessor: 'time',
|
||||
width: 100,
|
||||
className: 'user',
|
||||
align: 'right',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'billingStatus',
|
||||
Header: intl.get('timesheets.column.billing_status'),
|
||||
accessor: 'billing_status',
|
||||
width: 140,
|
||||
className: 'billingStatus',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DataTable, TableFastCell } from 'components';
|
||||
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
import { useTimesheetColumns, ActionsMenu } from './components';
|
||||
import { TableStyle } from '../../../../../common';
|
||||
import withSettings from '../../../../Settings/withSettings';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
const Timesheet = [
|
||||
{
|
||||
id: 1,
|
||||
data: '2020-01-01',
|
||||
task: 'Task 1',
|
||||
user: 'User 1',
|
||||
time: '12:00Am',
|
||||
billingStatus: '',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Timesheet DataTable.
|
||||
* @returns
|
||||
*/
|
||||
function TimesheetDataTable({
|
||||
// #withSettings
|
||||
timesheetsTableSize,
|
||||
}) {
|
||||
// Retrieve timesheet table columns.
|
||||
const columns = useTimesheetColumns();
|
||||
|
||||
// Handle delete timesheet.
|
||||
const handleDeleteTimesheet = () => {};
|
||||
|
||||
return (
|
||||
<TimesheetsTable
|
||||
columns={columns}
|
||||
data={Timesheet}
|
||||
// loading={}
|
||||
// headerLoading={}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
ContextMenu={ActionsMenu}
|
||||
TableCellRenderer={TableFastCell}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableRowsRenderer={TableVirtualizedListRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
vListrowHeight={timesheetsTableSize === 'small' ? 32 : 40}
|
||||
vListOverscanRowCount={0}
|
||||
styleName={TableStyle.Constrant}
|
||||
payload={{
|
||||
onDelete: handleDeleteTimesheet,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSettings(({ timesheetsSettings }) => ({
|
||||
timesheetsTableSize: timesheetsSettings?.tableSize,
|
||||
})),
|
||||
)(TimesheetDataTable);
|
||||
|
||||
const TimesheetsTable = styled(DataTable)`
|
||||
.table .tbody {
|
||||
.tbody-inner .tr.no-results {
|
||||
.td {
|
||||
padding: 2rem 0;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
font-weight: 400;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody-inner {
|
||||
.tr .td:not(:first-child) {
|
||||
border-left: 1px solid #e6e6e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -17,15 +17,15 @@ function ProjectTabs({
|
||||
changePageTitle,
|
||||
}) {
|
||||
const {
|
||||
state: { name },
|
||||
state: { projectName, projectId },
|
||||
} = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
changePageTitle(name);
|
||||
}, [changePageTitle, name]);
|
||||
changePageTitle(projectName);
|
||||
}, [changePageTitle, projectName]);
|
||||
|
||||
return (
|
||||
<ProjectDetailProvider>
|
||||
<ProjectDetailProvider projectId={projectId}>
|
||||
<ProjectDetailActionsBar />
|
||||
<DashboardPageContent>
|
||||
<ProjectDetailTabs />
|
||||
|
||||
Reference in New Issue
Block a user