re-structure to monorepo.

This commit is contained in:
a.bouhuolia
2023-02-03 01:02:31 +02:00
parent 8242ec64ba
commit 7a0a13f9d5
10400 changed files with 46966 additions and 17223 deletions

View File

@@ -0,0 +1,172 @@
// @ts-nocheck
import React from 'react';
import {
Button,
NavbarGroup,
Classes,
NavbarDivider,
Intent,
Alignment,
} from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
If,
Can,
Icon,
DashboardRowsHeightButton,
DashboardActionViewsList,
DashboardActionsBar,
DashboardFilterButton,
AdvancedFilterPopover,
FormattedMessage as T,
} from '@/components';
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
import { useRefreshExpenses } from '@/hooks/query/expenses';
import { useExpensesListContext } from './ExpensesListProvider';
import withExpenses from './withExpenses';
import withExpensesActions from './withExpensesActions';
import withSettingsActions from '@/containers/Settings/withSettingsActions';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import withSettings from '@/containers/Settings/withSettings';
import { compose } from '@/utils';
/**
* Expenses actions bar.
*/
function ExpensesActionsBar({
//#withExpensesActions
setExpensesTableState,
// #withExpenses
expensesFilterConditions,
// #withSettings
expensesTableSize,
// #withSettingsActions
addSetting,
}) {
// History context.
const history = useHistory();
// Expenses list context.
const { expensesViews, fields } = useExpensesListContext();
// Expenses refresh action.
const { refresh } = useRefreshExpenses();
// Handles the new expense buttn click.
const onClickNewExpense = () => {
history.push('/expenses/new');
};
// Handle delete button click.
const handleBulkDelete = () => {};
// Handles the tab chaning.
const handleTabChange = (view) => {
setExpensesTableState({
viewSlug: view ? view.slug : null,
});
};
// Handle click a refresh
const handleRefreshBtnClick = () => {
refresh();
};
// Handle table row size change.
const handleTableRowSizeChange = (size) => {
addSetting('expenses', 'tableSize', size);
};
return (
<DashboardActionsBar>
<NavbarGroup>
<DashboardActionViewsList
resourceName={'expenses'}
views={expensesViews}
allMenuItem={true}
onChange={handleTabChange}
/>
<NavbarDivider />
<Can I={ExpenseAction.Create} a={AbilitySubject.Expense}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="plus" />}
text={<T id={'new_expense'} />}
onClick={onClickNewExpense}
/>
</Can>
<AdvancedFilterPopover
advancedFilterProps={{
conditions: expensesFilterConditions,
defaultFieldKey: 'reference_no',
fields: fields,
onFilterChange: (filterConditions) => {
setExpensesTableState({ filterRoles: filterConditions });
},
}}
>
<DashboardFilterButton
conditionsCount={expensesFilterConditions.length}
/>
</AdvancedFilterPopover>
<If condition={false}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="trash-16" iconSize={16} />}
text={<T id={'delete'} />}
intent={Intent.DANGER}
onClick={handleBulkDelete}
/>
</If>
<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" iconSize={16} />}
text={<T id={'import'} />}
/>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="file-export-16" iconSize={16} />}
text={<T id={'export'} />}
/>
<NavbarDivider />
<DashboardRowsHeightButton
initialValue={expensesTableSize}
onChange={handleTableRowSizeChange}
/>
<NavbarDivider />
</NavbarGroup>
<NavbarGroup align={Alignment.RIGHT}>
<Button
className={Classes.MINIMAL}
icon={<Icon icon="refresh-16" iconSize={14} />}
onClick={handleRefreshBtnClick}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default compose(
withDialogActions,
withExpensesActions,
withSettingsActions,
withExpenses(({ expensesTableState }) => ({
expensesFilterConditions: expensesTableState.filterRoles,
})),
withSettings(({ expenseSettings }) => ({
expensesTableSize: expenseSettings?.tableSize,
})),
)(ExpensesActionsBar);

View File

@@ -0,0 +1,149 @@
// @ts-nocheck
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import { compose } from '@/utils';
import { useExpensesListContext } from './ExpensesListProvider';
import { useMemorizedColumnsWidths } from '@/hooks';
import {
DashboardContentTable,
DataTable,
TableSkeletonRows,
TableSkeletonHeader,
} from '@/components';
import { TABLES } from '@/constants/tables';
import ExpensesEmptyStatus from './ExpensesEmptyStatus';
import withDashboardActions from '@/containers/Dashboard/withDashboardActions';
import withExpensesActions from './withExpensesActions';
import withAlertsActions from '@/containers/Alert/withAlertActions';
import withDrawerActions from '@/containers/Drawer/withDrawerActions';
import withSettings from '@/containers/Settings/withSettings';
import { ActionsMenu, useExpensesTableColumns } from './components';
/**
* Expenses datatable.
*/
function ExpensesDataTable({
// #withExpensesActions
setExpensesTableState,
// #withDrawerActions
openDrawer,
// #withAlertsActions
openAlert,
// #withSettings
expensesTableSize,
}) {
// Expenses list context.
const {
expenses,
pagination,
isExpensesLoading,
isExpensesFetching,
isEmptyStatus,
} = useExpensesListContext();
const history = useHistory();
// Expenses table columns.
const columns = useExpensesTableColumns();
// Local storage memorizing columns widths.
const [initialColumnsWidths, , handleColumnResizing] =
useMemorizedColumnsWidths(TABLES.EXPENSES);
// Handle fetch data of manual jouranls datatable.
const handleFetchData = useCallback(
({ pageIndex, pageSize, sortBy }) => {
setExpensesTableState({
pageIndex,
pageSize,
sortBy,
});
},
[setExpensesTableState],
);
// Handle the expense publish action.
const handlePublishExpense = (expense) => {
openAlert('expense-publish', { expenseId: expense.id });
};
// Handle the expense edit action.
const handleEditExpense = ({ id }) => {
history.push(`/expenses/${id}/edit`);
};
// Handle the expense delete action.
const handleDeleteExpense = (expense) => {
openAlert('expense-delete', { expenseId: expense.id });
};
// Handle view detail expense.
const handleViewDetailExpense = ({ id }) => {
openDrawer('expense-drawer', {
expenseId: id,
});
};
// Handle cell click.
const handleCellClick = (cell, event) => {
openDrawer('expense-drawer', { expenseId: cell.row.original.id });
};
// Display empty status instead of the table.
if (isEmptyStatus) {
return <ExpensesEmptyStatus />;
}
return (
<DashboardContentTable>
<DataTable
columns={columns}
data={expenses}
loading={isExpensesLoading}
headerLoading={isExpensesLoading}
progressBarLoading={isExpensesFetching}
selectionColumn={true}
noInitialFetch={true}
sticky={true}
onFetchData={handleFetchData}
pagination={true}
manualSortBy={true}
manualPagination={true}
pagesCount={pagination.pagesCount}
autoResetSortBy={false}
autoResetPage={false}
TableLoadingRenderer={TableSkeletonRows}
TableHeaderSkeletonRenderer={TableSkeletonHeader}
ContextMenu={ActionsMenu}
onCellClick={handleCellClick}
initialColumnsWidths={initialColumnsWidths}
onColumnResizing={handleColumnResizing}
size={expensesTableSize}
payload={{
onPublish: handlePublishExpense,
onDelete: handleDeleteExpense,
onEdit: handleEditExpense,
onViewDetails: handleViewDetailExpense,
}}
/>
</DashboardContentTable>
);
}
export default compose(
withDashboardActions,
withAlertsActions,
withDrawerActions,
withExpensesActions,
withSettings(({ expenseSettings }) => ({
expensesTableSize: expenseSettings?.tableSize,
})),
)(ExpensesDataTable);

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import React from 'react';
import { Alignment, Navbar, NavbarGroup } from '@blueprintjs/core';
import { DashboardViewsTabs } from '@/components';
import { useExpensesListContext } from './ExpensesListProvider';
import withExpenses from './withExpenses';
import withExpensesActions from './withExpensesActions';
import { compose, transfromViewsToTabs } from '@/utils';
/**
* Expesne views tabs.
*/
function ExpenseViewTabs({
// #withExpensesActions
setExpensesTableState,
// #withExpenses
expensesCurrentView,
}) {
// Expenses list context.
const { expensesViews } = useExpensesListContext();
// Handle the tabs change.
const handleTabChange = (viewSlug) => {
setExpensesTableState({
viewSlug: viewSlug || null,
});
};
const tabs = transfromViewsToTabs(expensesViews);
// Handle click a new view tab.
const handleClickNewView = () => {};
return (
<Navbar className={'navbar--dashboard-views'}>
<NavbarGroup align={Alignment.LEFT}>
<DashboardViewsTabs
currentViewSlug={expensesCurrentView}
resourceName={'expenses'}
tabs={tabs}
onNewViewTabClick={handleClickNewView}
onChange={handleTabChange}
/>
</NavbarGroup>
</Navbar>
);
}
export default compose(
withExpensesActions,
withExpenses(({ expensesTableState }) => ({
expensesCurrentView: expensesTableState.viewSlug,
})),
)(ExpenseViewTabs);

View File

@@ -0,0 +1,40 @@
// @ts-nocheck
import React from 'react';
import { Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import { EmptyStatus, Can, FormattedMessage as T } from '@/components';
import { AbilitySubject, ExpenseAction } from '@/constants/abilityOption';
export default function InvoicesEmptyStatus() {
const history = useHistory();
return (
<EmptyStatus
title={<T id={'expenses.empty_status.title'} />}
description={
<p>
<T id={'expenses.empty_status.description'} />
</p>
}
action={
<>
<Can I={ExpenseAction.Create} a={AbilitySubject.Expense}>
<Button
intent={Intent.PRIMARY}
large={true}
onClick={() => {
history.push('/expenses/new');
}}
>
<T id={'new_expense'} />
</Button>
<Button intent={Intent.NONE} large={true}>
<T id={'learn_more'} />
</Button>
</Can>
</>
}
/>
);
}

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import React, { useEffect } from 'react';
import '@/style/pages/Expense/List.scss';
import { DashboardPageContent } from '@/components';
import ExpenseActionsBar from './ExpenseActionsBar';
import ExpenseViewTabs from './ExpenseViewTabs';
import ExpenseDataTable from './ExpenseDataTable';
import withExpenses from './withExpenses';
import withExpensesActions from './withExpensesActions';
import { compose, transformTableStateToQuery } from '@/utils';
import { ExpensesListProvider } from './ExpensesListProvider';
/**
* Expenses list.
*/
function ExpensesList({
// #withExpenses
expensesTableState,
expensesTableStateChanged,
// #withExpensesActions
resetExpensesTableState,
}) {
// Resets the accounts table state once the page unmount.
useEffect(
() => () => {
resetExpensesTableState();
},
[resetExpensesTableState],
);
return (
<ExpensesListProvider
query={transformTableStateToQuery(expensesTableState)}
tableStateChanged={expensesTableStateChanged}
>
<ExpenseActionsBar />
<DashboardPageContent>
<ExpenseViewTabs />
<ExpenseDataTable />
</DashboardPageContent>
</ExpensesListProvider>
);
}
export default compose(
withExpenses(({ expensesTableState, expensesTableStateChanged }) => ({
expensesTableState,
expensesTableStateChanged,
})),
withExpensesActions,
)(ExpensesList);

View File

@@ -0,0 +1,67 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { isEmpty } from 'lodash';
import { DashboardInsider } from '@/components/Dashboard';
import { useExpenses, useResourceMeta, useResourceViews } from '@/hooks/query';
import { getFieldsFromResourceMeta } from '@/utils';
const ExpensesListContext = createContext();
/**
* Accounts chart data provider.
*/
function ExpensesListProvider({ query, tableStateChanged, ...props }) {
// Fetch accounts resource views and fields.
const { data: expensesViews, isLoading: isViewsLoading } =
useResourceViews('expenses');
// Fetches the expenses with pagination meta.
const {
data: { expenses, pagination, filterMeta },
isLoading: isExpensesLoading,
isFetching: isExpensesFetching,
} = useExpenses(query, { keepPreviousData: true });
// Fetch the expenses resource fields.
const {
data: resourceMeta,
isLoading: isResourceMetaLoading,
isFetching: isResourceMetaFetching,
} = useResourceMeta('expenses');
// Detarmines the datatable empty status.
const isEmptyStatus =
isEmpty(expenses) && !isExpensesLoading && !tableStateChanged;
// Provider payload.
const provider = {
expensesViews,
expenses,
pagination,
fields: getFieldsFromResourceMeta(resourceMeta.fields),
resourceMeta,
isResourceMetaLoading,
isResourceMetaFetching,
isViewsLoading,
isExpensesLoading,
isExpensesFetching,
isEmptyStatus,
};
return (
<DashboardInsider
loading={isViewsLoading || isResourceMetaLoading}
name={'expenses'}
>
<ExpensesListContext.Provider value={provider} {...props} />
</DashboardInsider>
);
}
const useExpensesListContext = () => React.useContext(ExpensesListContext);
export { ExpensesListProvider, useExpensesListContext };

View File

@@ -0,0 +1,193 @@
// @ts-nocheck
import React from 'react';
import intl from 'react-intl-universal';
import {
Intent,
Button,
Classes,
Popover,
Tooltip,
Position,
Tag,
MenuItem,
Menu,
MenuDivider,
} from '@blueprintjs/core';
import clsx from 'classnames';
import { CLASSES } from '@/constants/classes';
import { ExpenseAction, AbilitySubject } from '@/constants/abilityOption';
import {
FormatDateCell,
FormattedMessage as T,
Icon,
If,
Can,
} from '@/components';
import { safeCallback } from '@/utils';
/**
* Description accessor.
*/
export function DescriptionAccessor(row) {
return (
<If condition={row.description}>
<Tooltip
className={Classes.TOOLTIP_INDICATOR}
content={row.description}
position={Position.TOP}
hoverOpenDelay={250}
>
<Icon icon={'file-alt'} iconSize={16} />
</Tooltip>
</If>
);
}
/**
* Actions menu.
*/
export function ActionsMenu({
row: { original },
payload: { onPublish, onEdit, onDelete, onViewDetails },
}) {
return (
<Menu>
<MenuItem
icon={<Icon icon="reader-18" />}
text={intl.get('view_details')}
onClick={safeCallback(onViewDetails, original)}
/>
<Can I={ExpenseAction.Edit} a={AbilitySubject.Expense}>
<MenuDivider />
<If condition={!original.is_published}>
<MenuItem
icon={<Icon icon={'arrow-to-top'} size={16} />}
text={intl.get('publish_expense')}
onClick={safeCallback(onPublish, original)}
/>
</If>
</Can>
<Can I={ExpenseAction.Edit} a={AbilitySubject.Expense}>
<MenuItem
icon={<Icon icon="pen-18" />}
text={intl.get('edit_expense')}
onClick={safeCallback(onEdit, original)}
/>
</Can>
<Can I={ExpenseAction.Delete} a={AbilitySubject.Expense}>
<MenuDivider />
<MenuItem
icon={<Icon icon="trash-16" iconSize={16} />}
text={intl.get('delete_expense')}
intent={Intent.DANGER}
onClick={safeCallback(onDelete, original)}
/>
</Can>
</Menu>
);
}
/**
* Actions cell.
*/
export function ActionsCell(props) {
return (
<Popover
content={<ActionsMenu {...props} />}
position={Position.RIGHT_BOTTOM}
>
<Button icon={<Icon icon="more-h-16" iconSize={16} />} />
</Popover>
);
}
/**
* Publish accessor.
*/
export function PublishAccessor(row) {
return row.is_published ? (
<Tag round={true} minimal={true}>
<T id={'published'} />
</Tag>
) : (
<Tag round={true} minimal={true} intent={Intent.WARNING}>
<T id={'draft'} />
</Tag>
);
}
/**
* Expense account accessor.
*/
export function ExpenseAccountAccessor(expense) {
if (expense.categories.length === 1) {
return expense.categories[0].expense_account.name;
} else if (expense.categories.length > 1) {
return <T id={'expense.column.multi_categories'} />;
}
}
/**
* Retrieve the expenses table columns.
*/
export function useExpensesTableColumns() {
return React.useMemo(
() => [
{
id: 'payment_date',
Header: intl.get('payment_date'),
accessor: 'payment_date',
Cell: FormatDateCell,
width: 140,
className: 'payment_date',
clickable: true,
},
{
id: 'amount',
Header: intl.get('full_amount'),
accessor: 'formatted_amount',
className: 'amount',
align: 'right',
width: 150,
clickable: true,
className: clsx(CLASSES.FONT_BOLD),
},
{
id: 'payment_account',
Header: intl.get('payment_account'),
accessor: 'payment_account.name',
className: 'payment_account',
width: 150,
clickable: true,
},
{
id: 'expense_account',
Header: intl.get('expense_account'),
accessor: ExpenseAccountAccessor,
width: 160,
className: 'expense_account',
disableSortBy: true,
clickable: true,
},
{
id: 'published',
Header: intl.get('publish'),
accessor: PublishAccessor,
width: 100,
className: 'publish',
clickable: true,
},
{
id: 'description',
Header: intl.get('description'),
accessor: DescriptionAccessor,
width: 150,
className: 'description',
disableSortBy: true,
clickable: true,
},
],
[],
);
}

View File

@@ -0,0 +1,20 @@
// @ts-nocheck
import { connect } from 'react-redux';
import {
expensesTableStateChangedFactory,
getExpensesTableStateFactory,
} from '@/store/expenses/expenses.selectors';
export default (mapState) => {
const getExpensesTableState = getExpensesTableStateFactory();
const expensesTableStateChanged = expensesTableStateChangedFactory();
const mapStateToProps = (state, props) => {
const mapped = {
expensesTableState: getExpensesTableState(state, props),
expensesTableStateChanged: expensesTableStateChanged(state, props),
};
return mapState ? mapState(mapped, state, props) : mapped;
};
return connect(mapStateToProps);
};

View File

@@ -0,0 +1,13 @@
// @ts-nocheck
import { connect } from 'react-redux';
import {
setExpensesTableState,
resetExpensesTableState,
} from '@/store/expenses/expenses.actions';
const mapDispatchToProps = (dispatch) => ({
setExpensesTableState: (state) => dispatch(setExpensesTableState(state)),
resetExpensesTableState: (state) => dispatch(resetExpensesTableState(state)),
});
export default connect(null, mapDispatchToProps);