mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-20 06:40:31 +00:00
fix issues.
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
|||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { useRouteMatch } from 'react-router-dom';
|
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||||
import DialogConnect from 'connectors/Dialog.connector';
|
import DialogConnect from 'connectors/Dialog.connector';
|
||||||
import AccountsConnect from 'connectors/Accounts.connector';
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
@@ -32,14 +32,21 @@ function AccountsActionsBar({
|
|||||||
onBulkDelete,
|
onBulkDelete,
|
||||||
onBulkArchive,
|
onBulkArchive,
|
||||||
}) {
|
}) {
|
||||||
const {path} = useRouteMatch();
|
const history = useHistory();
|
||||||
|
|
||||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||||
|
|
||||||
const accountsFields = getResourceFields('accounts');
|
const accountsFields = getResourceFields('accounts');
|
||||||
const [filterCount, setFilterCount] = useState(0);
|
const [filterCount, setFilterCount] = useState(0);
|
||||||
|
|
||||||
|
const onClickViewItem = (view) => {
|
||||||
|
history.push(view
|
||||||
|
? `/dashboard/accounts/${view.id}/custom_view` :
|
||||||
|
'/dashboard/accounts');
|
||||||
|
};
|
||||||
|
|
||||||
const viewsMenuItems = views.map((view) => {
|
const viewsMenuItems = views.map((view) => {
|
||||||
return (<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />);
|
return (<MenuItem onClick={() => onClickViewItem(view)} text={view.name} />);
|
||||||
});
|
});
|
||||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
||||||
|
|
||||||
@@ -131,15 +138,8 @@ function AccountsActionsBar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
// selectedRows: state.accounts.selectedRows
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
DialogConnect,
|
DialogConnect,
|
||||||
AccountsConnect,
|
AccountsConnect,
|
||||||
ResourceConnect,
|
ResourceConnect,
|
||||||
connect(mapStateToProps),
|
|
||||||
)(AccountsActionsBar);
|
)(AccountsActionsBar);
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import DataTable from 'components/DataTable';
|
|||||||
import Money from 'components/Money';
|
import Money from 'components/Money';
|
||||||
import { useUpdateEffect } from 'hooks';
|
import { useUpdateEffect } from 'hooks';
|
||||||
|
|
||||||
|
|
||||||
function AccountsDataTable({
|
function AccountsDataTable({
|
||||||
|
loading,
|
||||||
accounts,
|
accounts,
|
||||||
onDeleteAccount,
|
onDeleteAccount,
|
||||||
onInactiveAccount,
|
onInactiveAccount,
|
||||||
@@ -34,7 +36,6 @@ function AccountsDataTable({
|
|||||||
onFetchData,
|
onFetchData,
|
||||||
onSelectedRowsChange
|
onSelectedRowsChange
|
||||||
}) {
|
}) {
|
||||||
const {custom_view_id: customViewId} = useParams();
|
|
||||||
const [initialMount, setInitialMount] = useState(false);
|
const [initialMount, setInitialMount] = useState(false);
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useUpdateEffect(() => {
|
||||||
@@ -43,19 +44,6 @@ function AccountsDataTable({
|
|||||||
}
|
}
|
||||||
}, [accountsLoading, setInitialMount]);
|
}, [accountsLoading, setInitialMount]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const viewMeta = getViewItem(customViewId);
|
|
||||||
|
|
||||||
if (customViewId) {
|
|
||||||
changeCurrentView(customViewId);
|
|
||||||
setTopbarEditView(customViewId);
|
|
||||||
}
|
|
||||||
changePageSubtitle((customViewId && viewMeta) ? viewMeta.name : '');
|
|
||||||
}, [customViewId]);
|
|
||||||
|
|
||||||
// Clear page subtitle when unmount the page.
|
|
||||||
useEffect(() => () => { changePageSubtitle(''); }, []);
|
|
||||||
|
|
||||||
const handleEditAccount = useCallback((account) => () => {
|
const handleEditAccount = useCallback((account) => () => {
|
||||||
openDialog('account-form', { action: 'edit', id: account.id });
|
openDialog('account-form', { action: 'edit', id: account.id });
|
||||||
}, [openDialog]);
|
}, [openDialog]);
|
||||||
@@ -173,17 +161,19 @@ function AccountsDataTable({
|
|||||||
}, [onSelectedRowsChange]);
|
}, [onSelectedRowsChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DataTable
|
<LoadingIndicator loading={loading} mount={false}>
|
||||||
columns={columns}
|
<DataTable
|
||||||
data={accounts}
|
columns={columns}
|
||||||
onFetchData={handleDatatableFetchData}
|
data={accounts}
|
||||||
manualSortBy={true}
|
onFetchData={handleDatatableFetchData}
|
||||||
selectionColumn={selectionColumn}
|
manualSortBy={true}
|
||||||
expandable={true}
|
selectionColumn={selectionColumn}
|
||||||
treeGraph={true}
|
expandable={true}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange}
|
treeGraph={true}
|
||||||
loading={accountsLoading && !initialMount}
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
spinnerProps={{size: 30}} />
|
loading={accountsLoading && !initialMount}
|
||||||
|
spinnerProps={{size: 30}} />
|
||||||
|
</LoadingIndicator>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, {useEffect} from 'react';
|
import React, {useEffect, useCallback} from 'react';
|
||||||
import { useHistory } from 'react-router';
|
import { useHistory } from 'react-router';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
@@ -16,6 +16,7 @@ import { compose } from 'utils';
|
|||||||
import AccountsConnect from 'connectors/Accounts.connector';
|
import AccountsConnect from 'connectors/Accounts.connector';
|
||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
import {useUpdateEffect} from 'hooks';
|
import {useUpdateEffect} from 'hooks';
|
||||||
|
import ViewConnect from 'connectors/View.connector';
|
||||||
|
|
||||||
function AccountsViewsTabs({
|
function AccountsViewsTabs({
|
||||||
views,
|
views,
|
||||||
@@ -23,59 +24,74 @@ function AccountsViewsTabs({
|
|||||||
customViewChanged,
|
customViewChanged,
|
||||||
addAccountsTableQueries,
|
addAccountsTableQueries,
|
||||||
onViewChanged,
|
onViewChanged,
|
||||||
|
getViewItem,
|
||||||
|
changeCurrentView,
|
||||||
|
changePageSubtitle,
|
||||||
}) {
|
}) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { custom_view_id: customViewId } = useParams();
|
const { custom_view_id: customViewId = null } = useParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const viewMeta = getViewItem(customViewId);
|
||||||
|
|
||||||
|
changeCurrentView(customViewId || -1);
|
||||||
|
setTopbarEditView(customViewId);
|
||||||
|
changePageSubtitle((customViewId && viewMeta) ? viewMeta.name : '');
|
||||||
|
}, [customViewId]);
|
||||||
|
|
||||||
|
// Clear page subtitle when unmount the page.
|
||||||
|
useEffect(() => () => { changePageSubtitle(''); }, []);
|
||||||
|
|
||||||
|
// Handle click a new view tab.
|
||||||
const handleClickNewView = () => {
|
const handleClickNewView = () => {
|
||||||
setTopbarEditView(null);
|
setTopbarEditView(null);
|
||||||
history.push('/dashboard/custom_views/accounts/new');
|
history.push('/dashboard/custom_views/accounts/new');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle view tab link click.
|
||||||
const handleViewLinkClick = () => {
|
const handleViewLinkClick = () => {
|
||||||
setTopbarEditView(customViewId);
|
setTopbarEditView(customViewId);
|
||||||
}
|
};
|
||||||
|
|
||||||
useUpdateEffect(() => {
|
useEffect(() => {
|
||||||
customViewChanged && customViewChanged(customViewId);
|
customViewChanged && customViewChanged(customViewId);
|
||||||
|
|
||||||
addAccountsTableQueries({
|
addAccountsTableQueries({
|
||||||
custom_view_id: customViewId || null,
|
custom_view_id: customViewId,
|
||||||
});
|
});
|
||||||
|
}, [customViewId]);
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
onViewChanged && onViewChanged(customViewId);
|
onViewChanged && onViewChanged(customViewId);
|
||||||
}, [customViewId]);
|
}, [customViewId]);
|
||||||
|
|
||||||
useEffect(() => {
|
const tabs = views.map((view) => {
|
||||||
addAccountsTableQueries({
|
|
||||||
custom_view_id: customViewId,
|
|
||||||
})
|
|
||||||
}, [customViewId]);
|
|
||||||
|
|
||||||
const tabs = views.map(view => {
|
|
||||||
const baseUrl = '/dashboard/accounts';
|
const baseUrl = '/dashboard/accounts';
|
||||||
|
|
||||||
const link = (
|
const link = (
|
||||||
<Link
|
<Link
|
||||||
to={`${baseUrl}/${view.id}/custom_view`}
|
to={`${baseUrl}/${view.id}/custom_view`}
|
||||||
onClick={handleViewLinkClick}
|
onClick={handleViewLinkClick}
|
||||||
>{view.name}</Link>
|
>{ view.name }</Link>
|
||||||
);
|
);
|
||||||
return <Tab
|
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
||||||
id={`custom_view_${view.id}`}
|
|
||||||
title={link} />;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar className='navbar--dashboard-views'>
|
<Navbar className='navbar--dashboard-views'>
|
||||||
<NavbarGroup align={Alignment.LEFT}>
|
<NavbarGroup align={Alignment.LEFT}>
|
||||||
<Tabs
|
<Tabs
|
||||||
id='navbar'
|
id='navbar'
|
||||||
large={true}
|
large={true}
|
||||||
selectedTabId={`custom_view_${customViewId}`}
|
selectedTabId={customViewId ? `custom_view_${customViewId}` : 'all'}
|
||||||
className='tabs--dashboard-views'
|
className='tabs--dashboard-views'
|
||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
id='all'
|
id={'all'}
|
||||||
title={<Link to={`/dashboard/accounts`}>All</Link>} />
|
title={<Link to={`/dashboard/accounts`}>All</Link>}
|
||||||
|
onClick={handleViewLinkClick}
|
||||||
{tabs}
|
/>
|
||||||
|
{ tabs }
|
||||||
<Button
|
<Button
|
||||||
className='button--new-view'
|
className='button--new-view'
|
||||||
icon={<Icon icon='plus' />}
|
icon={<Icon icon='plus' />}
|
||||||
@@ -91,4 +107,5 @@ function AccountsViewsTabs({
|
|||||||
export default compose(
|
export default compose(
|
||||||
AccountsConnect,
|
AccountsConnect,
|
||||||
DashboardConnect,
|
DashboardConnect,
|
||||||
|
ViewConnect,
|
||||||
)(AccountsViewsTabs);
|
)(AccountsViewsTabs);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import PreferencesContent from 'components/Preferences/PreferencesContent';
|
|||||||
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
||||||
import Search from 'containers/Dashboard/GeneralSearch/Search';
|
import Search from 'containers/Dashboard/GeneralSearch/Search';
|
||||||
|
|
||||||
export default function () {
|
export default function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<div className='dashboard'>
|
<div className='dashboard'>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ export default function DashboardContentRoute() {
|
|||||||
<Switch>
|
<Switch>
|
||||||
{ routes.map((route, index) => (
|
{ routes.map((route, index) => (
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact={route.exact}
|
||||||
// key={index}
|
key={index}
|
||||||
path={`${route.path}`}
|
path={`${route.path}`}
|
||||||
component={route.component} />
|
component={route.component} />
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Switch, useRouteMatch } from 'react-router-dom';
|
import { Route, Switch, Redirect } from 'react-router-dom';
|
||||||
import preferencesRoutes from 'routes/preferences'
|
import preferencesRoutes from 'routes/preferences'
|
||||||
|
|
||||||
export default function DashboardContentRoute() {
|
export default function DashboardContentRoute() {
|
||||||
|
const defaultTab = '/dashboard/preferences/general';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route pathname="/dashboard/preferences">
|
<Route pathname="/dashboard/preferences">
|
||||||
|
<Redirect from='/dashboard/preferences' to={defaultTab} />
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
{ preferencesRoutes.map((route, index) => (
|
{ preferencesRoutes.map((route, index) => (
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,11 +1,31 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router-dom';
|
||||||
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
|
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
|
||||||
|
import UsersActions from 'containers/Dashboard/Preferences/UsersActions';
|
||||||
|
import CurrenciesActions from 'containers/Dashboard/Preferences/CurrenciesActions';
|
||||||
|
|
||||||
|
|
||||||
export default function PreferencesTopbar() {
|
export default function PreferencesTopbar() {
|
||||||
return (
|
return (
|
||||||
<div class="dashboard__preferences-topbar">
|
<div class="dashboard__preferences-topbar">
|
||||||
<h2>Accounts</h2>
|
<h2>Accounts</h2>
|
||||||
|
|
||||||
|
<div class="preferences__topbar-actions">
|
||||||
|
<Route pathname="/dashboard/preferences">
|
||||||
|
<Switch>
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={'/dashboard/preferences/users'}
|
||||||
|
component={UsersActions} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={'/dashboard/preferences/currencies'}
|
||||||
|
component={CurrenciesActions} />
|
||||||
|
</Switch>
|
||||||
|
</Route>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dashboard__topbar-user">
|
<div class="dashboard__topbar-user">
|
||||||
<DashboardTopbarUser />
|
<DashboardTopbarUser />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,34 +23,4 @@ export default [
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
href: '/dashboard/preferences/accounts',
|
href: '/dashboard/preferences/accounts',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: 'Credit Notes',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/credit_note',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Debit Notes',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/debit_note',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Accountant',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/accountant',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Accounts',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/accounts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Credit Notes',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/credit_note',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: 'Debit Notes',
|
|
||||||
disabled: false,
|
|
||||||
href: '/dashboard/preferences/debit_note',
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export const mapStateToProps = (state, props) => ({
|
|||||||
|
|
||||||
export const mapDispatchToProps = (dispatch) => ({
|
export const mapDispatchToProps = (dispatch) => ({
|
||||||
requestSubmitMedia: (form, config) => dispatch(submitMedia({ form, config })),
|
requestSubmitMedia: (form, config) => dispatch(submitMedia({ form, config })),
|
||||||
requestDeleteMedia: (id) => dispatch(deleteMedia({ id })),
|
requestDeleteMedia: (ids) => dispatch(deleteMedia({ ids })),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps);
|
export default connect(mapStateToProps, mapDispatchToProps);
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
Position,
|
Position,
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import AuthenticationToaster from 'components/AppToaster';
|
import Toaster from 'components/AppToaster';
|
||||||
import ErrorMessage from 'components/ErrorMessage';
|
import ErrorMessage from 'components/ErrorMessage';
|
||||||
import AuthInsider from 'containers/Authentication/AuthInsider';
|
import AuthInsider from 'containers/Authentication/AuthInsider';
|
||||||
import Icon from 'components/Icon';
|
import Icon from 'components/Icon';
|
||||||
@@ -69,7 +69,6 @@ function Login({
|
|||||||
message: `The email and password you entered did not match our records.
|
message: `The email and password you entered did not match our records.
|
||||||
Please double-check and try again.`,
|
Please double-check and try again.`,
|
||||||
intent: Intent.DANGER,
|
intent: Intent.DANGER,
|
||||||
position: Position.BOTTOM,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (errors.find((e) => e.type === ERRORS_TYPES.USER_INACTIVE)) {
|
if (errors.find((e) => e.type === ERRORS_TYPES.USER_INACTIVE)) {
|
||||||
@@ -79,7 +78,7 @@ function Login({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
toastBuilders.forEach(builder => {
|
toastBuilders.forEach(builder => {
|
||||||
AuthenticationToaster.show(builder);
|
Toaster.show(builder);
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
});
|
});
|
||||||
@@ -114,7 +113,6 @@ function Login({
|
|||||||
<InputGroup
|
<InputGroup
|
||||||
intent={(errors.crediential && touched.crediential) && Intent.DANGER}
|
intent={(errors.crediential && touched.crediential) && Intent.DANGER}
|
||||||
large={true}
|
large={true}
|
||||||
placeholder={'name@company.com'}
|
|
||||||
{...getFieldProps('crediential')}
|
{...getFieldProps('crediential')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
@@ -130,7 +128,6 @@ function Login({
|
|||||||
large={true}
|
large={true}
|
||||||
intent={(errors.password && touched.password) && Intent.DANGER}
|
intent={(errors.password && touched.password) && Intent.DANGER}
|
||||||
type={shown ? 'text' : 'password'}
|
type={shown ? 'text' : 'password'}
|
||||||
placeholder={'password'}
|
|
||||||
{...getFieldProps('password')}
|
{...getFieldProps('password')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ function AccountsChart({
|
|||||||
const [bulkDelete, setBulkDelete] = useState(false);
|
const [bulkDelete, setBulkDelete] = useState(false);
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
|
|
||||||
|
const [tableLoading, setTableLoading] = useState(false);
|
||||||
|
|
||||||
// Fetch accounts resource views and fields.
|
// Fetch accounts resource views and fields.
|
||||||
const fetchHook = useAsync(async () => {
|
const fetchHook = useAsync(async () => {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -66,7 +68,8 @@ function AccountsChart({
|
|||||||
setDeleteAccount(false);
|
setDeleteAccount(false);
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'cannot_delete_predefined_account'
|
message: 'cannot_delete_predefined_account',
|
||||||
|
intent: Intent.DANGER,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||||
@@ -138,7 +141,11 @@ function AccountsChart({
|
|||||||
|
|
||||||
// Refetch accounts data table when current custom view changed.
|
// Refetch accounts data table when current custom view changed.
|
||||||
const handleViewChanged = useCallback(() => {
|
const handleViewChanged = useCallback(() => {
|
||||||
fetchAccountsHook.execute();
|
setTableLoading(true);
|
||||||
|
|
||||||
|
fetchAccountsHook.execute().finally(() => {
|
||||||
|
setTableLoading(false);
|
||||||
|
});
|
||||||
}, [fetchAccountsHook]);
|
}, [fetchAccountsHook]);
|
||||||
|
|
||||||
// Handle fetch data of accounts datatable.
|
// Handle fetch data of accounts datatable.
|
||||||
@@ -177,7 +184,8 @@ function AccountsChart({
|
|||||||
onRestoreAccount={handleRestoreAccount}
|
onRestoreAccount={handleRestoreAccount}
|
||||||
onEditAccount={handleEditAccount}
|
onEditAccount={handleEditAccount}
|
||||||
onFetchData={handleFetchData}
|
onFetchData={handleFetchData}
|
||||||
onSelectedRowsChange={handleSelectedRowsChange} />
|
onSelectedRowsChange={handleSelectedRowsChange}
|
||||||
|
loading={tableLoading} />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import {
|
|||||||
Intent,
|
Intent,
|
||||||
TextArea,
|
TextArea,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Checkbox
|
Checkbox,
|
||||||
|
Position
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { Select } from '@blueprintjs/select';
|
import { Select } from '@blueprintjs/select';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
@@ -92,7 +93,8 @@ function AccountFormDialog({
|
|||||||
}).then((response) => {
|
}).then((response) => {
|
||||||
closeDialog(name);
|
closeDialog(name);
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_account_has_been_edited'
|
message: 'the_account_has_been_edited',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}).catch((errors) => {
|
}).catch((errors) => {
|
||||||
@@ -103,7 +105,9 @@ function AccountFormDialog({
|
|||||||
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
requestSubmitAccount({ form: { ...omit(values, exclude) } }).then(response => {
|
||||||
closeDialog(name);
|
closeDialog(name);
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
message: 'the_account_has_been_submit'
|
message: 'the_account_has_been_submit',
|
||||||
|
intent: Intent.SUCCESS,
|
||||||
|
position: Position.BOTTOM,
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}).catch((errors) => {
|
}).catch((errors) => {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
@@ -90,9 +90,9 @@ function InviteUserDialog({
|
|||||||
fetchHook.execute();
|
fetchHook.execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDialogClosed = () => {
|
const onDialogClosed = useCallback(() => {
|
||||||
formik.resetForm();
|
formik.resetForm();
|
||||||
};
|
}, [formik.resetForm]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
closeDialog(name);
|
closeDialog(name);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
@@ -49,7 +49,14 @@ function UserFormDialog({
|
|||||||
)),
|
)),
|
||||||
};
|
};
|
||||||
|
|
||||||
const formik = useFormik({
|
const {
|
||||||
|
values,
|
||||||
|
errors,
|
||||||
|
touched,
|
||||||
|
resetForm,
|
||||||
|
getFieldProps,
|
||||||
|
handleSubmit,
|
||||||
|
} = useFormik({
|
||||||
enableReinitialize: true,
|
enableReinitialize: true,
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema,
|
validationSchema,
|
||||||
@@ -74,15 +81,13 @@ function UserFormDialog({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const { values, errors, touched } = useMemo(() => formik, [formik]);
|
|
||||||
|
|
||||||
const onDialogOpening = () => {
|
const onDialogOpening = () => {
|
||||||
fetchHook.execute();
|
fetchHook.execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDialogClosed = () => {
|
const onDialogClosed = useCallback(() => {
|
||||||
formik.resetForm();
|
resetForm();
|
||||||
};
|
}, [resetForm]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
closeDialog(name);
|
closeDialog(name);
|
||||||
@@ -103,19 +108,21 @@ function UserFormDialog({
|
|||||||
onClosed={onDialogClosed}
|
onClosed={onDialogClosed}
|
||||||
onOpening={onDialogOpening}
|
onOpening={onDialogOpening}
|
||||||
>
|
>
|
||||||
<form onSubmit={formik.handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<div className={Classes.DIALOG_BODY}>
|
<div className={Classes.DIALOG_BODY}>
|
||||||
|
<p class="mb2">Your teammate will get an email that gives them access to your team.</p>
|
||||||
|
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={'Email'}
|
label={'Email'}
|
||||||
className={'form-group--email'}
|
className={classNames('form-group--email', Classes.FILL)}
|
||||||
intent={errors.email && touched.email && Intent.DANGER}
|
intent={(errors.email && touched.email) && Intent.DANGER}
|
||||||
helperText={<ErrorMessage name='email' {...formik} />}
|
helperText={<ErrorMessage name='email' {...{errors, touched}} />}
|
||||||
inline={true}
|
inline={true}
|
||||||
>
|
>
|
||||||
<InputGroup
|
<InputGroup
|
||||||
medium={true}
|
medium={true}
|
||||||
intent={errors.email && touched.email && Intent.DANGER}
|
intent={(errors.email && touched.email) && Intent.DANGER}
|
||||||
{...formik.getFieldProps('email')}
|
{...getFieldProps('email')}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -79,6 +79,20 @@ const ItemsActionsBar = ({
|
|||||||
|
|
||||||
<NavbarDivider />
|
<NavbarDivider />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='plus' />}
|
||||||
|
text='New Item'
|
||||||
|
onClick={onClickNewItem}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className={Classes.MINIMAL}
|
||||||
|
icon={<Icon icon='plus' />}
|
||||||
|
text='New Category'
|
||||||
|
onClick={onClickNewCategory}
|
||||||
|
/>
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
content={filterDropdown}
|
content={filterDropdown}
|
||||||
interactionKind={PopoverInteractionKind.CLICK}
|
interactionKind={PopoverInteractionKind.CLICK}
|
||||||
@@ -91,19 +105,6 @@ const ItemsActionsBar = ({
|
|||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon={<Icon icon='plus' />}
|
|
||||||
text='New Item'
|
|
||||||
onClick={onClickNewItem}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className={Classes.MINIMAL}
|
|
||||||
icon={<Icon icon='plus' />}
|
|
||||||
text='New Category'
|
|
||||||
onClick={onClickNewCategory}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{hasSelectedRows && (
|
{hasSelectedRows && (
|
||||||
<Button
|
<Button
|
||||||
className={Classes.MINIMAL}
|
className={Classes.MINIMAL}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ const ItemCategoriesList = ({
|
|||||||
}, [setSelectedRows]);
|
}, [setSelectedRows]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardInsider loading={fetchHook.pending}>
|
<DashboardInsider loading={fetchHook.pending} name="items-categories">
|
||||||
<ItemsCategoryActionsBar
|
<ItemsCategoryActionsBar
|
||||||
views={views}
|
views={views}
|
||||||
onDeleteCategory={handelDeleteCategory}
|
onDeleteCategory={handelDeleteCategory}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ function ItemsList({
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchResourceViews('items'),
|
fetchResourceViews('items'),
|
||||||
fetchResourceFields('items'),
|
fetchResourceFields('items'),
|
||||||
])
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchItems = useAsync(async () => {
|
const fetchItems = useAsync(async () => {
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ function Currencies({ openDialog }) {
|
|||||||
return (
|
return (
|
||||||
<div className={'preferences__inside-content'}>
|
<div className={'preferences__inside-content'}>
|
||||||
<div className={'preferences__tabs'}>
|
<div className={'preferences__tabs'}>
|
||||||
<Button intent={Intent.PRIMARY} onClick={onClickNewCurrency}>
|
|
||||||
New Currency
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import React, {useCallback} from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DialogConnect from 'connectors/Dialog.connector';
|
||||||
|
import {compose} from 'utils';
|
||||||
|
|
||||||
|
function CurrenciesActions({
|
||||||
|
openDialog,
|
||||||
|
}) {
|
||||||
|
const handleClickNewCurrency = useCallback(() => {
|
||||||
|
openDialog('currency-form');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="users-actions">
|
||||||
|
<Button
|
||||||
|
icon={<Icon icon='plus' iconSize={12} />}
|
||||||
|
onClick={handleClickNewCurrency}
|
||||||
|
intent={Intent.PRIMARY}>
|
||||||
|
New Currency
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
DialogConnect,
|
||||||
|
)(CurrenciesActions);
|
||||||
@@ -19,8 +19,6 @@ import DialogConnect from 'connectors/Dialog.connector';
|
|||||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||||
import LoadingIndicator from 'components/LoadingIndicator';
|
import LoadingIndicator from 'components/LoadingIndicator';
|
||||||
import DataTable from 'components/DataTable';
|
import DataTable from 'components/DataTable';
|
||||||
import Currencies from './Currencies';
|
|
||||||
import useAsync from 'hooks/async';
|
|
||||||
import AppToaster from 'components/AppToaster';
|
import AppToaster from 'components/AppToaster';
|
||||||
|
|
||||||
function CurrenciesList({
|
function CurrenciesList({
|
||||||
@@ -109,11 +107,9 @@ function CurrenciesList({
|
|||||||
const handleDatatableFetchData = useCallback(() => {
|
const handleDatatableFetchData = useCallback(() => {
|
||||||
onFetchData && onFetchData();
|
onFetchData && onFetchData();
|
||||||
}, []);
|
}, []);
|
||||||
console.log({ currencies }, 'X');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LoadingIndicator>
|
<LoadingIndicator>
|
||||||
<Currencies />
|
|
||||||
<DataTable
|
<DataTable
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={Object.values(currencies)}
|
data={Object.values(currencies)}
|
||||||
|
|||||||
@@ -6,26 +6,12 @@ import connector from 'connectors/UsersPreferences.connector';
|
|||||||
function UsersPreferences({ openDialog }) {
|
function UsersPreferences({ openDialog }) {
|
||||||
const onChangeTabs = (currentTabId) => {};
|
const onChangeTabs = (currentTabId) => {};
|
||||||
|
|
||||||
const onClickNewUser = useCallback(() => {
|
|
||||||
openDialog('user-form');
|
|
||||||
}, [openDialog]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class='preferences__inside-content preferences__inside-content--users-roles'>
|
<div class='preferences__inside-content preferences__inside-content--users-roles'>
|
||||||
<div class='preferences__tabs'>
|
<div class='preferences__tabs'>
|
||||||
<Tabs animate={true} large={true} onChange={onChangeTabs}>
|
<Tabs animate={true} onChange={onChangeTabs}>
|
||||||
<Tab id='users' title='Users' />
|
<Tab id='users' title='Users' />
|
||||||
<Tab id='roles' title='Roles' />
|
<Tab id='roles' title='Roles' />
|
||||||
|
|
||||||
<div class='preferences__tabs-extra-actions'>
|
|
||||||
<Button intent={Intent.PRIMARY} onClick={onClickNewUser}>
|
|
||||||
Invite User
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button intent={Intent.PRIMARY} onClick={onClickNewUser}>
|
|
||||||
New Role
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
<PreferencesSubContent preferenceTab='users' />
|
<PreferencesSubContent preferenceTab='users' />
|
||||||
|
|||||||
38
client/src/containers/Dashboard/Preferences/UsersActions.js
Normal file
38
client/src/containers/Dashboard/Preferences/UsersActions.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import React, {useCallback} from 'react';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Intent,
|
||||||
|
} from '@blueprintjs/core';
|
||||||
|
import Icon from 'components/Icon';
|
||||||
|
import DialogConnect from 'connectors/Dialog.connector';
|
||||||
|
import {compose} from 'utils';
|
||||||
|
|
||||||
|
function UsersActions({
|
||||||
|
openDialog,
|
||||||
|
closeDialog,
|
||||||
|
}) {
|
||||||
|
const onClickNewUser = useCallback(() => {
|
||||||
|
openDialog('user-form');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div claass="preferences-actions">
|
||||||
|
<Button
|
||||||
|
icon={<Icon icon='plus' iconSize={12} />}
|
||||||
|
onClick={onClickNewUser}
|
||||||
|
intent={Intent.PRIMARY}>
|
||||||
|
Invite User
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
icon={<Icon icon='plus' iconSize={12} />}
|
||||||
|
onClick={onClickNewUser}>
|
||||||
|
New Role
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
DialogConnect,
|
||||||
|
)(UsersActions);
|
||||||
@@ -83,10 +83,7 @@ function UsersListPreferences({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirmUserDelete = () => {
|
const handleConfirmUserDelete = () => {
|
||||||
if (!deleteUserState) {
|
if (!deleteUserState) { return; }
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
requestDeleteUser(deleteUserState.id).then((response) => {
|
requestDeleteUser(deleteUserState.id).then((response) => {
|
||||||
setDeleteUserState(false);
|
setDeleteUserState(false);
|
||||||
AppToaster.show({
|
AppToaster.show({
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ const useMedia = ({ saveCallback, deleteCallback }) => {
|
|||||||
}, [files, openProgressToast, saveCallback]);
|
}, [files, openProgressToast, saveCallback]);
|
||||||
|
|
||||||
const deleteMedia = useCallback(() => {
|
const deleteMedia = useCallback(() => {
|
||||||
|
debugger;
|
||||||
return deletedFiles.length > 0
|
return deletedFiles.length > 0
|
||||||
? deleteCallback(deletedFiles) : Promise.resolve();
|
? deleteCallback(deletedFiles) : Promise.resolve();
|
||||||
}, [deletedFiles, deleteCallback]);
|
}, [deletedFiles, deleteCallback]);
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const fetchView = ({ id }) => {
|
|||||||
|
|
||||||
export const fetchResourceViews = ({ resourceSlug }) => {
|
export const fetchResourceViews = ({ resourceSlug }) => {
|
||||||
return (dispatch) => new Promise((resolve, reject) => {
|
return (dispatch) => new Promise((resolve, reject) => {
|
||||||
ApiService.get('views', { query: { resource_name: resourceSlug } })
|
ApiService.get('views', { params: { resource_name: resourceSlug } })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: t.RESOURCE_VIEWS_SET,
|
type: t.RESOURCE_VIEWS_SET,
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export const submitMedia = ({ form, config }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteMedia = ({ id }) => {
|
export const deleteMedia = ({ ids }) => {
|
||||||
return (dispatch) => {
|
return (dispatch) => {
|
||||||
return ApiService.delete('media', { params: { id } });
|
return ApiService.delete('media', { params: { ids } });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -45,6 +45,7 @@ $pt-font-family: Noto Sans, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
|||||||
@import 'pages/manual-journals';
|
@import 'pages/manual-journals';
|
||||||
@import 'pages/item-category';
|
@import 'pages/item-category';
|
||||||
@import 'pages/items';
|
@import 'pages/items';
|
||||||
|
@import 'pages/items-categories';
|
||||||
@import 'pages/invite-form.scss';
|
@import 'pages/invite-form.scss';
|
||||||
@import "pages/currency";
|
@import "pages/currency";
|
||||||
@import "pages/invite-user.scss";
|
@import "pages/invite-user.scss";
|
||||||
|
|||||||
@@ -58,6 +58,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bp3-button-group.bp3-minimal .bp3-button{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.bp3-button{
|
.bp3-button{
|
||||||
|
|
||||||
&.bp3-intent-primary,
|
&.bp3-intent-primary,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
body{
|
body{
|
||||||
color: #444;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-heading{
|
.#{$ns}-heading{
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
.dashboard__insider--accounts-chart{
|
.dashboard__insider--accounts-chart{
|
||||||
|
|
||||||
.bigcapital-datatable{
|
.bigcapital-datatable{
|
||||||
|
|
||||||
.normal{
|
.normal{
|
||||||
.#{$ns}-icon{
|
.#{$ns}-icon{
|
||||||
color: #aaa;
|
color: #aaa;
|
||||||
@@ -17,7 +16,6 @@
|
|||||||
padding-bottom: 0.4rem;
|
padding-bottom: 0.4rem;
|
||||||
}
|
}
|
||||||
.account_name{
|
.account_name{
|
||||||
font-weight: 500;
|
|
||||||
|
|
||||||
.bp3-tooltip-indicator{
|
.bp3-tooltip-indicator{
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
&-user{
|
&-user{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 24px;
|
|
||||||
|
|
||||||
.#{$ns}-button{
|
.#{$ns}-button{
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
@@ -247,8 +246,9 @@
|
|||||||
|
|
||||||
h2{
|
h2{
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 200;
|
font-weight: 300;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: #555;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,8 +260,13 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 0 14px;
|
padding: 0;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
|
|
||||||
|
> a{
|
||||||
|
padding-left: 14px;
|
||||||
|
padding-right: 14px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}-tab-indicator-wrapper{
|
.#{$ns}-tab-indicator-wrapper{
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
|
|
||||||
.dialog--invite-form {
|
.dialog--invite-form {
|
||||||
&.bp3-dialog {
|
&.bp3-dialog {
|
||||||
width: 400px;
|
width: 450px;
|
||||||
}
|
}
|
||||||
&:not(.dialog--loading) .bp3-dialog-body {
|
&:not(.dialog--loading) .bp3-dialog-body {
|
||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
.bp3-dialog-body {
|
.bp3-dialog-body {
|
||||||
// margin-right: 50px;
|
|
||||||
.bp3-form-group.bp3-inline {
|
|
||||||
.bp3-label {
|
|
||||||
min-width: 70px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.form-group--email {
|
|
||||||
.bp3-form-content {
|
|
||||||
width: 250px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bp3-dialog-footer-actions {
|
.bp3-dialog-footer-actions {
|
||||||
margin-right: 30px;
|
margin-right: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
8
client/src/style/pages/items-categories.scss
Normal file
8
client/src/style/pages/items-categories.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
|
||||||
|
.dashboard__insider--items-categories{
|
||||||
|
|
||||||
|
.dashboard__actions-bar{
|
||||||
|
border-bottom: 2px solid #EAEAEA;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
.dashboard-content--preferences {
|
.dashboard-content--preferences {
|
||||||
margin-left: 430px;
|
margin-left: 430px;
|
||||||
height: 700px;
|
height: 700px;
|
||||||
width: 800px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -13,14 +12,34 @@
|
|||||||
|
|
||||||
&__inside-content {
|
&__inside-content {
|
||||||
.#{$ns}-tab-list {
|
.#{$ns}-tab-list {
|
||||||
border-bottom: 1px solid #fd0000;
|
border-bottom: 1px solid #E5E5E5;
|
||||||
padding-left: 15px;
|
padding-left: 15px;
|
||||||
|
padding-right: 15px;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
.#{$ns}-tab {
|
.#{$ns}-tab {
|
||||||
font-weight: 300;
|
font-weight: 400;
|
||||||
|
line-height: 44px;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__tabs-extra-actions{
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__topbar-actions{
|
||||||
|
margin-left: auto;
|
||||||
|
padding-right: 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
border-right: 1px solid #e5e5e5;
|
||||||
|
|
||||||
|
.bp3-button + .bp3-button{
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&-menu {
|
&-menu {
|
||||||
width: 374px;
|
width: 374px;
|
||||||
}
|
}
|
||||||
@@ -59,7 +78,8 @@
|
|||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
font-weight: 200;
|
font-weight: 300;
|
||||||
|
color: #555;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,7 +110,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.preferences__tabs-extra-actions {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ exports.up = function (knex) {
|
|||||||
table.text('note').nullable();
|
table.text('note').nullable();
|
||||||
table.integer('category_id').unsigned();
|
table.integer('category_id').unsigned();
|
||||||
table.integer('user_id').unsigned();
|
table.integer('user_id').unsigned();
|
||||||
table.string('attachment_file');
|
|
||||||
table.timestamps();
|
table.timestamps();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ exports.up = function (knex) {
|
|||||||
table.integer('view_id').unsigned();
|
table.integer('view_id').unsigned();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return knex.seed.run({
|
return knex.seed.run({
|
||||||
specific: 'seed_views_role.js',
|
specific: 'seed_views_roles.js',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ exports.seed = function(knex) {
|
|||||||
return knex('resource_fields').insert([
|
return knex('resource_fields').insert([
|
||||||
{ id: 1, label_name: 'Name', key: 'name', data_type: '', active: 1, predefined: 1 },
|
{ id: 1, label_name: 'Name', key: 'name', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 2, label_name: 'Code', key: 'code', data_type: '', active: 1, predefined: 1 },
|
{ id: 2, label_name: 'Code', key: 'code', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 3, label_name: 'Account Type', key: 'account_type_id', data_type: '', active: 1, predefined: 1 },
|
{ id: 3, label_name: 'Account Type', key: 'type', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 4, label_name: 'Description', key: 'description', data_type: '', active: 1, predefined: 1 },
|
{ id: 4, label_name: 'Description', key: 'description', data_type: '', active: 1, predefined: 1 },
|
||||||
{ id: 5, label_name: 'Account Normal', key: 'normal', data_type: 'string', active: 1, predefined: 1 },
|
{ id: 5, label_name: 'Account Normal', key: 'normal', data_type: 'string', active: 1, predefined: 1 },
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
label_name: 'Root Account Type',
|
label_name: 'Root Account Type',
|
||||||
key: 'root_account_type',
|
key: 'root_type',
|
||||||
data_type: 'string',
|
data_type: 'string',
|
||||||
active: 1,
|
active: 1,
|
||||||
predefined: 1,
|
predefined: 1,
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ exports.seed = (knex) => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
// Inserts seed entries
|
// Inserts seed entries
|
||||||
return knex('view_roles').insert([
|
return knex('view_roles').insert([
|
||||||
{ id: 1, field_id: 6, comparator: 'equals', value: 'asset', view_id: 1 },
|
{ id: 1, field_id: 6, index: 1, comparator: 'equals', value: 'asset', view_id: 1 },
|
||||||
{ id: 2, field_id: 6, comparator: 'equals', value: 'liability', view_id: 2 },
|
{ id: 2, field_id: 6, index: 1, comparator: 'equals', value: 'liability', view_id: 2 },
|
||||||
{ id: 3, field_id: 6, comparator: 'equals', value: 'equity', view_id: 3 },
|
{ id: 3, field_id: 6, index: 1, comparator: 'equals', value: 'equity', view_id: 3 },
|
||||||
{ id: 4, field_id: 6, comparator: 'equals', value: 'income', view_id: 4 },
|
{ id: 4, field_id: 6, index: 1, comparator: 'equals', value: 'income', view_id: 4 },
|
||||||
{ id: 5, field_id: 6, comparator: 'equals', value: 'expense', view_id: 5 },
|
{ id: 5, field_id: 6, index: 1, comparator: 'equals', value: 'expense', view_id: 5 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export default {
|
|||||||
}
|
}
|
||||||
const form = {
|
const form = {
|
||||||
custom_fields: [],
|
custom_fields: [],
|
||||||
|
media_ids: [],
|
||||||
...req.body,
|
...req.body,
|
||||||
};
|
};
|
||||||
const {
|
const {
|
||||||
@@ -90,6 +91,7 @@ export default {
|
|||||||
ResourceField,
|
ResourceField,
|
||||||
ItemCategory,
|
ItemCategory,
|
||||||
Item,
|
Item,
|
||||||
|
MediaLink,
|
||||||
} = req.models;
|
} = req.models;
|
||||||
const errorReasons = [];
|
const errorReasons = [];
|
||||||
|
|
||||||
@@ -146,6 +148,7 @@ export default {
|
|||||||
return res.boom.badRequest(null, { errors: errorReasons });
|
return res.boom.badRequest(null, { errors: errorReasons });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bulkSaveMediaLinks = [];
|
||||||
const item = await Item.query().insertAndFetch({
|
const item = await Item.query().insertAndFetch({
|
||||||
name: form.name,
|
name: form.name,
|
||||||
type: form.type,
|
type: form.type,
|
||||||
@@ -156,6 +159,20 @@ export default {
|
|||||||
currency_code: form.currency_code,
|
currency_code: form.currency_code,
|
||||||
note: form.note,
|
note: form.note,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
form.media_ids.forEach((mediaId) => {
|
||||||
|
const oper = MediaLink.query().insert({
|
||||||
|
model_name: 'Item',
|
||||||
|
media_id: mediaId,
|
||||||
|
model_id: item.id,
|
||||||
|
});
|
||||||
|
bulkSaveMediaLinks.push(oper);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save the media links.
|
||||||
|
await Promise.all([
|
||||||
|
...bulkSaveMediaLinks,
|
||||||
|
]);
|
||||||
return res.status(200).send({ id: item.id });
|
return res.status(200).send({ id: item.id });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -188,14 +205,14 @@ export default {
|
|||||||
code: 'validation_error', ...validationErrors,
|
code: 'validation_error', ...validationErrors,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { Account, Item, ItemCategory } = req.models;
|
const { Account, Item, ItemCategory, MediaLink } = req.models;
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
custom_fields: [],
|
custom_fields: [],
|
||||||
...req.body,
|
...req.body,
|
||||||
};
|
};
|
||||||
const item = await Item.query().findById(id);
|
const item = await Item.query().findById(id).withGraphFetched('media');
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
return res.boom.notFound(null, {
|
return res.boom.notFound(null, {
|
||||||
@@ -235,12 +252,12 @@ export default {
|
|||||||
if (attachment) {
|
if (attachment) {
|
||||||
const publicPath = 'storage/app/public/';
|
const publicPath = 'storage/app/public/';
|
||||||
const tenantPath = `${publicPath}${req.organizationId}`;
|
const tenantPath = `${publicPath}${req.organizationId}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fsPromises.unlink(`${tenantPath}/${item.attachmentFile}`);
|
await fsPromises.unlink(`${tenantPath}/${item.attachmentFile}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.log('error', 'Delete item attachment file delete failed.', { error });
|
Logger.log('error', 'Delete item attachment file delete failed.', { error });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await attachment.mv(`${tenantPath}/${attachment.md5}.png`);
|
await attachment.mv(`${tenantPath}/${attachment.md5}.png`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -262,6 +279,22 @@ export default {
|
|||||||
note: form.note,
|
note: form.note,
|
||||||
attachment_file: (attachment) ? item.attachmentFile : null,
|
attachment_file: (attachment) ? item.attachmentFile : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Save links of new inserted media that associated to the item model.
|
||||||
|
const itemMediaIds = item.media.map((m) => m.id);
|
||||||
|
const newInsertedMedia = difference(form.media_ids, itemMediaIds);
|
||||||
|
const bulkSaveMediaLink = [];
|
||||||
|
|
||||||
|
newInsertedMedia.forEach((mediaId) => {
|
||||||
|
const oper = MediaLink.query().insert({
|
||||||
|
model_name: 'Journal',
|
||||||
|
model_id: manualJournal.id,
|
||||||
|
media_id: mediaId,
|
||||||
|
});
|
||||||
|
bulkSaveMediaLink.push(oper);
|
||||||
|
});
|
||||||
|
await Promise.all([ ...newInsertedMedia ]);
|
||||||
|
|
||||||
return res.status(200).send({ id: updatedItem.id });
|
return res.status(200).send({ id: updatedItem.id });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
validationResult,
|
validationResult,
|
||||||
} from 'express-validator';
|
} from 'express-validator';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { difference } from 'lodash';
|
||||||
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
import asyncMiddleware from '@/http/middleware/asyncMiddleware';
|
||||||
import Logger from '@/services/Logger';
|
import Logger from '@/services/Logger';
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ export default {
|
|||||||
this.upload.validation,
|
this.upload.validation,
|
||||||
asyncMiddleware(this.upload.handler));
|
asyncMiddleware(this.upload.handler));
|
||||||
|
|
||||||
router.delete('/delete/:id',
|
router.delete('/',
|
||||||
this.delete.validation,
|
this.delete.validation,
|
||||||
asyncMiddleware(this.delete.handler));
|
asyncMiddleware(this.delete.handler));
|
||||||
|
|
||||||
@@ -109,7 +110,8 @@ export default {
|
|||||||
*/
|
*/
|
||||||
delete: {
|
delete: {
|
||||||
validation: [
|
validation: [
|
||||||
param('id').exists().isNumeric().toInt(),
|
query('ids').exists().isArray(),
|
||||||
|
query('ids.*').exists().isNumeric().toInt(),
|
||||||
],
|
],
|
||||||
async handler(req, res) {
|
async handler(req, res) {
|
||||||
const validationErrors = validationResult(req);
|
const validationErrors = validationResult(req);
|
||||||
@@ -120,26 +122,37 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
const { Media, MediaLink } = req.models;
|
const { Media, MediaLink } = req.models;
|
||||||
const { id } = req.params;
|
const ids = Array.isArray(req.query.ids) ? req.query.ids : [req.query.ids];
|
||||||
const media = await Media.query().where('id', id).first();
|
const media = await Media.query().whereIn('id', ids);
|
||||||
|
const mediaIds = media.map((m) => m.id);
|
||||||
|
const notFoundMedia = difference(ids, mediaIds);
|
||||||
|
|
||||||
if (!media) {
|
if (notFoundMedia.length) {
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
errors: [{ type: 'MEDIA.ID.NOT.FOUND', code: 200 }],
|
errors: [{ type: 'MEDIA.IDS.NOT.FOUND', code: 200, ids: notFoundMedia }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const publicPath = 'storage/app/public/';
|
const publicPath = 'storage/app/public/';
|
||||||
const tenantPath = `${publicPath}${req.organizationId}`;
|
const tenantPath = `${publicPath}${req.organizationId}`;
|
||||||
|
const unlinkOpers = [];
|
||||||
|
|
||||||
try {
|
media.forEach((mediaModel) => {
|
||||||
await fsPromises.unlink(`${tenantPath}/${media.attachmentFile}`);
|
const oper = fsPromises.unlink(`${tenantPath}/${mediaModel.attachmentFile}`);
|
||||||
Logger.log('error', 'Attachment file has been deleted.');
|
unlinkOpers.push(oper);
|
||||||
} catch (error) {
|
});
|
||||||
Logger.log('error', 'Delete item attachment file delete failed.', { error });
|
await Promise.all(unlinkOpers).then((resolved) => {
|
||||||
}
|
resolved.forEach(() => {
|
||||||
|
Logger.log('error', 'Attachment file has been deleted.');
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((errors) => {
|
||||||
|
errors.forEach((error) => {
|
||||||
|
Logger.log('error', 'Delete item attachment file delete failed.', { error });
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
await MediaLink.query().where('media_id', media.id).delete();
|
await MediaLink.query().whereIn('media_id', mediaIds).delete();
|
||||||
await Media.query().where('id', media.id).delete();
|
await Media.query().whereIn('id', mediaIds).delete();
|
||||||
|
|
||||||
return res.status(200).send();
|
return res.status(200).send();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ export default class Item extends TenantModel {
|
|||||||
return 'items';
|
return 'items';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model modifiers.
|
||||||
|
*/
|
||||||
static get modifiers() {
|
static get modifiers() {
|
||||||
const TABLE_NAME = Item.tableName;
|
const TABLE_NAME = Item.tableName;
|
||||||
|
|
||||||
@@ -29,6 +32,7 @@ export default class Item extends TenantModel {
|
|||||||
* Relationship mapping.
|
* Relationship mapping.
|
||||||
*/
|
*/
|
||||||
static get relationMappings() {
|
static get relationMappings() {
|
||||||
|
const Media = require('@/models/Media');
|
||||||
const Account = require('@/models/Account');
|
const Account = require('@/models/Account');
|
||||||
const ItemCategory = require('@/models/ItemCategory');
|
const ItemCategory = require('@/models/ItemCategory');
|
||||||
|
|
||||||
@@ -71,6 +75,19 @@ export default class Item extends TenantModel {
|
|||||||
to: 'accounts.id',
|
to: 'accounts.id',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
media: {
|
||||||
|
relation: Model.ManyToManyRelation,
|
||||||
|
modelClass: this.relationBindKnex(Media.default),
|
||||||
|
join: {
|
||||||
|
from: 'items.id',
|
||||||
|
through: {
|
||||||
|
from: 'media_links.model_id',
|
||||||
|
to: 'media_links.media_id',
|
||||||
|
},
|
||||||
|
to: 'media.id',
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user