mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-19 14:20:31 +00:00
feat: add timesheet & project details.
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
NavbarGroup,
|
||||
Alignment,
|
||||
} from '@blueprintjs/core';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import {
|
||||
Icon,
|
||||
FormattedMessage as T,
|
||||
DashboardRowsHeightButton,
|
||||
} from 'components';
|
||||
import withSettings from '../../../Settings/withSettings';
|
||||
import withSettingsActions from '../../../Settings/withSettingsActions';
|
||||
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Project detail actions bar.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectDetailActionsBar({
|
||||
// #withSettings
|
||||
timeSheetsTableSize,
|
||||
|
||||
// #withSettingsActions
|
||||
addSetting,
|
||||
}) {
|
||||
// Handle new transaction button click.
|
||||
const handleNewTransactionBtnClick = () => {};
|
||||
|
||||
// Handle table row size change.
|
||||
const handleTableRowSizeChange = (size) => {
|
||||
addSetting('timeSheets', 'tableSize', size);
|
||||
};
|
||||
|
||||
// Handle the refresh button click.
|
||||
const handleRefreshBtnClick = () => {};
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={<T id={'projcet_details.action.new_transaction'} />}
|
||||
onClick={handleNewTransactionBtnClick}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={<T id={'projcet_details.action.log_time'} />}
|
||||
// onClick={}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
text={<T id={'projcet_details.action.edit_project'} />}
|
||||
// onClick={}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'print-16'} iconSize={'16'} />}
|
||||
text={<T id={'print'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-import-16'} />}
|
||||
text={<T id={'import'} />}
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon={'file-export-16'} iconSize={'16'} />}
|
||||
text={<T id={'export'} />}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<DashboardRowsHeightButton
|
||||
initialValue={timeSheetsTableSize}
|
||||
onChange={handleTableRowSizeChange}
|
||||
/>
|
||||
<NavbarDivider />
|
||||
<Button icon={<Icon icon="more-vert" iconSize={16} />} minimal={true} />
|
||||
</NavbarGroup>
|
||||
<NavbarGroup align={Alignment.RIGHT}>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon="refresh-16" iconSize={14} />}
|
||||
onClick={handleRefreshBtnClick}
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
export default compose(
|
||||
withSettingsActions,
|
||||
withSettings(({ timeSheetsSettings }) => ({
|
||||
timeSheetsTableSize: timeSheetsSettings?.tableSize,
|
||||
})),
|
||||
)(ProjectDetailActionsBar);
|
||||
@@ -0,0 +1,25 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
const ProjectDetailContext = React.createContext();
|
||||
|
||||
/**
|
||||
* Project detail provider.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectDetailProvider({
|
||||
// #ownProps
|
||||
...props
|
||||
}) {
|
||||
// State provider.
|
||||
const provider = {};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ProjectDetailContext.Provider value={provider} {...props} />
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const useProjectDetailContext = () => React.useContext(ProjectDetailContext);
|
||||
|
||||
export { ProjectDetailProvider, useProjectDetailContext };
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Tabs, Tab } from '@blueprintjs/core';
|
||||
import TimeSheetDataTable from './TimeSheet/TimeSheetDataTable';
|
||||
|
||||
/**
|
||||
* Project detail tabs.
|
||||
* @returns
|
||||
*/
|
||||
export default function ProjectDetailTabs() {
|
||||
return (
|
||||
<ProjectTabsContent>
|
||||
<Tabs
|
||||
animate={true}
|
||||
large={true}
|
||||
renderActiveTabPanelOnly={true}
|
||||
defaultSelectedTabId={'overview'}
|
||||
>
|
||||
<Tab id="overview" title={intl.get('project_details.label.overview')} />
|
||||
<Tab
|
||||
id="timesheet"
|
||||
title={intl.get('project_details.label.timesheet')}
|
||||
panel={<TimeSheetDataTable />}
|
||||
/>
|
||||
<Tab
|
||||
id="purchases"
|
||||
title={intl.get('project_details.label.purchases')}
|
||||
/>
|
||||
<Tab id="sales" title={intl.get('project_details.label.sales')} />
|
||||
<Tab id="journals" title={intl.get('project_details.label.journals')} />
|
||||
</Tabs>
|
||||
</ProjectTabsContent>
|
||||
);
|
||||
}
|
||||
|
||||
const ProjectTabsContent = styled.div`
|
||||
.bp3-tabs {
|
||||
.bp3-tab-list {
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
padding: 0 20px;
|
||||
border-bottom: 1px solid #d2dce2;
|
||||
|
||||
&.bp3-large > .bp3-tab {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #646a7d;
|
||||
margin: 0 1rem;
|
||||
|
||||
&[aria-selected='true'],
|
||||
&:not([aria-disabled='true']):hover {
|
||||
color: #0052cc;
|
||||
}
|
||||
}
|
||||
.bp3-tab-indicator-wrapper .bp3-tab-indicator {
|
||||
height: 2px;
|
||||
bottom: -2px;
|
||||
}
|
||||
}
|
||||
.bp3-tab-panel {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,117 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DataTable, TableFastCell, DashboardContentTable } from 'components';
|
||||
import TableVirtualizedListRows from 'components/Datatable/TableVirtualizedRows';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
import TableSkeletonHeader from 'components/Datatable/TableHeaderSkeleton';
|
||||
import { useTimeSheetColumns, ActionMenu } 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: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
data: '2021-01-01',
|
||||
task: 'Task 2',
|
||||
user: 'User 2',
|
||||
time: '12:00Am',
|
||||
billingStatus: '',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
data: '2022-01-01',
|
||||
task: 'Task 3',
|
||||
user: 'User 3',
|
||||
time: '12:00Am',
|
||||
billingStatus: '',
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* TimeSheet DataTable.
|
||||
* @returns
|
||||
*/
|
||||
function TimeSheetDataTable({
|
||||
// #withSettings
|
||||
timeSheetsTableSize,
|
||||
}) {
|
||||
// Retrieve timesheet table columns.
|
||||
const columns = useTimeSheetColumns();
|
||||
|
||||
return (
|
||||
<DashboardContentTable>
|
||||
<TimeSheetsTable
|
||||
columns={columns}
|
||||
data={TimeSheet}
|
||||
// loading={}
|
||||
// headerLoading={}
|
||||
noInitialFetch={true}
|
||||
sticky={true}
|
||||
expandColumnSpace={1}
|
||||
expandToggleColumn={2}
|
||||
selectionColumnWidth={45}
|
||||
TableCellRenderer={TableFastCell}
|
||||
TableLoadingRenderer={TableSkeletonRows}
|
||||
TableRowsRenderer={TableVirtualizedListRows}
|
||||
TableHeaderSkeletonRenderer={TableSkeletonHeader}
|
||||
vListrowHeight={timeSheetsTableSize === 'small' ? 32 : 40}
|
||||
vListOverscanRowCount={0}
|
||||
styleName={TableStyle.Constrant}
|
||||
// payload={{}}
|
||||
/>
|
||||
</DashboardContentTable>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSettings(({ timeSheetsSettings }) => ({
|
||||
timeSheetsTableSize: timeSheetsSettings?.tableSize,
|
||||
})),
|
||||
)(TimeSheetDataTable);
|
||||
|
||||
const DashboardConstrantTable = styled(DataTable)`
|
||||
.table {
|
||||
.thead {
|
||||
.th {
|
||||
background: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.tbody {
|
||||
.tr:last-child .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const TimeSheetsTable = styled(DashboardConstrantTable)`
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { FormatDateCell } from 'components';
|
||||
import { Menu, MenuDivider, MenuItem, Intent } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Table actions cell.
|
||||
*/
|
||||
export const ActionMenu = ({ row: { original }, payload: {} }) => <Menu></Menu>;
|
||||
|
||||
/**
|
||||
* Retrieve timesheet list columns columns.
|
||||
*/
|
||||
export const useTimeSheetColumns = () => {
|
||||
return React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: intl.get('timesheets.column.date'),
|
||||
accessor: 'date',
|
||||
Cell: FormatDateCell,
|
||||
width: 115,
|
||||
className: 'date',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'task',
|
||||
Header: intl.get('timesheets.column.task'),
|
||||
accessor: 'task',
|
||||
width: 115,
|
||||
className: 'task',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
Header: intl.get('timesheets.column.user'),
|
||||
accessor: 'user',
|
||||
width: 115,
|
||||
className: 'user',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'time',
|
||||
Header: intl.get('timesheets.column.time'),
|
||||
accessor: 'time',
|
||||
width: 115,
|
||||
className: 'user',
|
||||
align: 'right',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
{
|
||||
id: 'billingStatus',
|
||||
Header: intl.get('timesheets.column.billing_status'),
|
||||
accessor: 'billing_status',
|
||||
width: 160,
|
||||
className: 'billingStatus',
|
||||
clickable: true,
|
||||
textOverview: true,
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
};
|
||||
37
src/containers/Projects/containers/ProjectDetails/index.tsx
Normal file
37
src/containers/Projects/containers/ProjectDetails/index.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
//@ts-nocheck
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import ProjectDetailActionsBar from './ProjectDetailActionsBar';
|
||||
import ProjectDetailTabs from './ProjectDetailTabs';
|
||||
import { DashboardPageContent } from 'components';
|
||||
import { ProjectDetailProvider } from './ProjectDetailProvider';
|
||||
import withDashboardActions from 'containers/Dashboard/withDashboardActions';
|
||||
import { compose } from 'utils';
|
||||
|
||||
/**
|
||||
* Project tabs.
|
||||
* @returns
|
||||
*/
|
||||
function ProjectTabs({
|
||||
// #withDashboardActions
|
||||
changePageTitle,
|
||||
}) {
|
||||
const {
|
||||
state: { original },
|
||||
} = useLocation();
|
||||
|
||||
React.useEffect(() => {
|
||||
changePageTitle(original.name);
|
||||
}, [changePageTitle, original]);
|
||||
|
||||
return (
|
||||
<ProjectDetailProvider>
|
||||
<ProjectDetailActionsBar />
|
||||
<DashboardPageContent>
|
||||
<ProjectDetailTabs />
|
||||
</DashboardPageContent>
|
||||
</ProjectDetailProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(withDashboardActions)(ProjectTabs);
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { DataTable } from 'components';
|
||||
import { TABLES } from 'common/tables';
|
||||
import TableSkeletonRows from 'components/Datatable/TableSkeletonRows';
|
||||
@@ -44,11 +45,17 @@ function ProjectsDataTable({
|
||||
// #withSettings
|
||||
projectsTableSize,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
// Retrieve projects table columns.
|
||||
const columns = useProjectsListColumns();
|
||||
|
||||
// Handle cell click.
|
||||
const handleCellClick = (cell, event) => {};
|
||||
const handleCellClick = ({ row: { original } }) => {
|
||||
return history.push(`/projects/${original?.id}/details`, {
|
||||
original,
|
||||
});
|
||||
};
|
||||
|
||||
// Handle edit project.
|
||||
const handleEditProject = (project) => {
|
||||
@@ -66,6 +73,13 @@ function ProjectsDataTable({
|
||||
const [initialColumnsWidths, , handleColumnResizing] =
|
||||
useMemorizedColumnsWidths(TABLES.PROJECTS);
|
||||
|
||||
// Handle view detail project.
|
||||
const handleViewDetailProject = (project) => {
|
||||
return history.push(`/projects/${project.id}/details`, {
|
||||
original: project.name,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
@@ -85,6 +99,7 @@ function ProjectsDataTable({
|
||||
onColumnResizing={handleColumnResizing}
|
||||
size={projectsTableSize}
|
||||
payload={{
|
||||
onViewDetails: handleViewDetailProject,
|
||||
onEdit: handleEditProject,
|
||||
onNewTask: handleNewTaskButtonClick,
|
||||
}}
|
||||
|
||||
@@ -22,7 +22,8 @@ export default (mapState) => {
|
||||
creditNoteSettings: state.settings.data.creditNote,
|
||||
vendorsCreditNoteSetting: state.settings.data.vendorCredit,
|
||||
warehouseTransferSettings: state.settings.data.warehouseTransfers,
|
||||
projectSettings:state.settings.data.projects
|
||||
projectSettings:state.settings.data.projects,
|
||||
timeSheetsSettings:state.settings.data.timeSheets
|
||||
};
|
||||
return mapState ? mapState(mapped, state, props) : mapped;
|
||||
};
|
||||
|
||||
@@ -2069,5 +2069,18 @@
|
||||
"task.schema.label.task_name": "Task name",
|
||||
"task.schema.label.task_house": "Task house",
|
||||
"task.schema.label.charge": "Charge",
|
||||
"task.schema.label.amount": "Amount"
|
||||
"task.schema.label.amount": "Amount",
|
||||
"projcet_details.action.new_transaction": "New Transaction",
|
||||
"projcet_details.action.log_time": "Log Time",
|
||||
"projcet_details.action.edit_project": "Edit Project",
|
||||
"project_details.label.overview": "Overview",
|
||||
"project_details.label.timesheet": "Timesheet",
|
||||
"project_details.label.purchases": "Purchases",
|
||||
"project_details.label.sales": "Sales",
|
||||
"project_details.label.journals": "Journals",
|
||||
"timesheets.column.date": "Date",
|
||||
"timesheets.column.task": "Task",
|
||||
"timesheets.column.user": "User",
|
||||
"timesheets.column.time": "Time",
|
||||
"timesheets.column.billing_status": "Billing Status"
|
||||
}
|
||||
@@ -969,6 +969,15 @@ export const getDashboardRoutes = () => [
|
||||
),
|
||||
pageTitle: intl.get('sidebar.transactions_locaking'),
|
||||
},
|
||||
|
||||
{
|
||||
path: '/projects/:id/details',
|
||||
component: lazy(() =>
|
||||
import('../containers/Projects/containers/ProjectDetails'),
|
||||
),
|
||||
sidebarExpand: false,
|
||||
backLink: true,
|
||||
},
|
||||
{
|
||||
path: '/projects',
|
||||
component: lazy(() =>
|
||||
@@ -976,6 +985,7 @@ export const getDashboardRoutes = () => [
|
||||
),
|
||||
pageTitle: intl.get('sidebar.projects'),
|
||||
},
|
||||
|
||||
// Homepage
|
||||
{
|
||||
path: `/`,
|
||||
|
||||
@@ -64,6 +64,9 @@ const initialState = {
|
||||
projects: {
|
||||
tableSize: 'medium',
|
||||
},
|
||||
timeSheets: {
|
||||
tableSize: 'medium',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user