mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
WIP Version 0.0.1
This commit is contained in:
@@ -1,145 +0,0 @@
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
MenuItem,
|
||||
Menu,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent,
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { connect } from 'react-redux';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import AccountsConnect from 'connectors/Accounts.connector';
|
||||
import {compose} from 'utils';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import ResourceConnect from 'connectors/Resource.connector';
|
||||
|
||||
function AccountsActionsBar({
|
||||
openDialog,
|
||||
views,
|
||||
selectedRows = [],
|
||||
getResourceFields,
|
||||
addAccountsTableQueries,
|
||||
onFilterChanged,
|
||||
onBulkDelete,
|
||||
onBulkArchive,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
|
||||
const onClickNewAccount = () => { openDialog('account-form', {}); };
|
||||
|
||||
const accountsFields = getResourceFields('accounts');
|
||||
const [filterCount, setFilterCount] = useState(0);
|
||||
|
||||
const onClickViewItem = (view) => {
|
||||
history.push(view
|
||||
? `/dashboard/accounts/${view.id}/custom_view` :
|
||||
'/dashboard/accounts');
|
||||
};
|
||||
|
||||
const viewsMenuItems = views.map((view) => {
|
||||
return (<MenuItem onClick={() => onClickViewItem(view)} text={view.name} />);
|
||||
});
|
||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: accountsFields,
|
||||
onFilterChange: (filterConditions) => {
|
||||
setFilterCount(filterConditions.length || 0);
|
||||
addAccountsTableQueries({
|
||||
filter_roles: filterConditions || '',
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
},
|
||||
});
|
||||
|
||||
const handleBulkArchive = useCallback(() => {
|
||||
onBulkArchive && onBulkArchive(selectedRows.map(r => r.id));
|
||||
}, [onBulkArchive, selectedRows]);
|
||||
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||
}, [onBulkDelete, selectedRows]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Popover
|
||||
content={<Menu>{viewsMenuItems}</Menu>}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||
icon={<Icon icon='table' />}
|
||||
text='Table Views'
|
||||
rightIcon={'caret-down'}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
<NavbarDivider />
|
||||
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='plus' />}
|
||||
text='New Account'
|
||||
onClick={onClickNewAccount}
|
||||
/>
|
||||
<Popover
|
||||
minimal={true}
|
||||
content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}>
|
||||
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text={filterCount <= 0 ? 'Filter' : `${filterCount} filters applied`}
|
||||
icon={ <Icon icon="filter" /> }/>
|
||||
</Popover>
|
||||
|
||||
{hasSelectedRows && (
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='archive' iconSize={15} />}
|
||||
text='Archive'
|
||||
onClick={handleBulkArchive}
|
||||
/>
|
||||
)}
|
||||
{hasSelectedRows && (
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='trash' iconSize={15} />}
|
||||
text='Delete'
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleBulkDelete}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-import' />}
|
||||
text='Import'
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-export' />}
|
||||
text='Export'
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
DialogConnect,
|
||||
AccountsConnect,
|
||||
ResourceConnect,
|
||||
)(AccountsActionsBar);
|
||||
@@ -1,185 +0,0 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
Classes,
|
||||
Tooltip,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { compose } from 'utils';
|
||||
import AccountsConnect from 'connectors/Accounts.connector';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import ViewConnect from 'connectors/View.connector';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Money from 'components/Money';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
|
||||
function AccountsDataTable({
|
||||
loading,
|
||||
accounts,
|
||||
onDeleteAccount,
|
||||
onInactiveAccount,
|
||||
openDialog,
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
getViewItem,
|
||||
setTopbarEditView,
|
||||
accountsLoading,
|
||||
onFetchData,
|
||||
onSelectedRowsChange
|
||||
}) {
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!accountsLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [accountsLoading, setInitialMount]);
|
||||
|
||||
const handleEditAccount = useCallback((account) => () => {
|
||||
openDialog('account-form', { action: 'edit', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const handleNewParentAccount = useCallback((account) => {
|
||||
openDialog('account-form', { action: 'new_child', id: account.id });
|
||||
}, [openDialog]);
|
||||
|
||||
const actionMenuList = useCallback((account) => (
|
||||
<Menu>
|
||||
<MenuItem text='View Details' />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text='Edit Account'
|
||||
onClick={handleEditAccount(account)} />
|
||||
<MenuItem
|
||||
text='New Account'
|
||||
onClick={() => handleNewParentAccount(account)} />
|
||||
<MenuDivider />
|
||||
<MenuItem
|
||||
text='Inactivate Account'
|
||||
onClick={() => onInactiveAccount(account)} />
|
||||
<MenuItem
|
||||
text='Delete Account'
|
||||
onClick={() => onDeleteAccount(account)} />
|
||||
</Menu>
|
||||
), [handleEditAccount, onDeleteAccount, onInactiveAccount]);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Account Name',
|
||||
accessor: row => {
|
||||
return (row.description) ?
|
||||
(<Tooltip
|
||||
className={Classes.TOOLTIP_INDICATOR}
|
||||
content={row.description}
|
||||
position={Position.RIGHT_TOP}
|
||||
hoverOpenDelay={500}>
|
||||
{ row.name }
|
||||
</Tooltip>) : row.name;
|
||||
},
|
||||
className: 'account_name',
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
id: 'code',
|
||||
Header: 'Code',
|
||||
accessor: 'code',
|
||||
className: 'code',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
id: 'type',
|
||||
Header: 'Type',
|
||||
accessor: 'type.name',
|
||||
className: 'type',
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
id: 'normal',
|
||||
Header: 'Normal',
|
||||
Cell: ({ cell }) => {
|
||||
const account = cell.row.original;
|
||||
const type = account.type ? account.type.normal : '';
|
||||
const arrowDirection = type === 'credit' ? 'down' : 'up';
|
||||
|
||||
return (<Icon icon={`arrow-${arrowDirection}`} />);
|
||||
},
|
||||
className: 'normal',
|
||||
width: 75,
|
||||
},
|
||||
{
|
||||
id: 'balance',
|
||||
Header: 'Balance',
|
||||
Cell: ({ cell }) => {
|
||||
const account = cell.row.original;
|
||||
const {balance = null} = account;
|
||||
|
||||
return (balance) ?
|
||||
(<span>
|
||||
<Money amount={balance.amount} currency={balance.currency_code} />
|
||||
</span>) :
|
||||
(<span class="placeholder">--</span>);
|
||||
},
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_TOP}>
|
||||
<Button icon={<Icon icon='ellipsis-h' />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
}
|
||||
], [actionMenuList]);
|
||||
|
||||
const selectionColumn = useMemo(() => ({
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
}), [])
|
||||
|
||||
const handleDatatableFetchData = useCallback((...params) => {
|
||||
onFetchData && onFetchData(...params);
|
||||
}, []);
|
||||
|
||||
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||
}, [onSelectedRowsChange]);
|
||||
|
||||
return (
|
||||
<LoadingIndicator loading={loading} mount={false}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={accounts}
|
||||
onFetchData={handleDatatableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={selectionColumn}
|
||||
expandable={true}
|
||||
treeGraph={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
loading={accountsLoading && !initialMount}
|
||||
spinnerProps={{size: 30}} />
|
||||
</LoadingIndicator>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
AccountsConnect,
|
||||
DialogConnect,
|
||||
DashboardConnect,
|
||||
ViewConnect
|
||||
)(AccountsDataTable);
|
||||
@@ -1,111 +0,0 @@
|
||||
import React, {useEffect, useCallback} from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import { connect } from 'react-redux';
|
||||
import {
|
||||
Alignment,
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import AccountsConnect from 'connectors/Accounts.connector';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import {useUpdateEffect} from 'hooks';
|
||||
import ViewConnect from 'connectors/View.connector';
|
||||
|
||||
function AccountsViewsTabs({
|
||||
views,
|
||||
setTopbarEditView,
|
||||
customViewChanged,
|
||||
addAccountsTableQueries,
|
||||
onViewChanged,
|
||||
getViewItem,
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
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 = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/dashboard/custom_views/accounts/new');
|
||||
};
|
||||
|
||||
// Handle view tab link click.
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
customViewChanged && customViewChanged(customViewId);
|
||||
|
||||
addAccountsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
}, [customViewId]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
const tabs = views.map((view) => {
|
||||
const baseUrl = '/dashboard/accounts';
|
||||
|
||||
const link = (
|
||||
<Link
|
||||
to={`${baseUrl}/${view.id}/custom_view`}
|
||||
onClick={handleViewLinkClick}
|
||||
>{ view.name }</Link>
|
||||
);
|
||||
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<Navbar className='navbar--dashboard-views'>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<Tabs
|
||||
id='navbar'
|
||||
large={true}
|
||||
selectedTabId={customViewId ? `custom_view_${customViewId}` : 'all'}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id={'all'}
|
||||
title={<Link to={`/dashboard/accounts`}>All</Link>}
|
||||
onClick={handleViewLinkClick}
|
||||
/>
|
||||
{ tabs }
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
AccountsConnect,
|
||||
DashboardConnect,
|
||||
ViewConnect,
|
||||
)(AccountsViewsTabs);
|
||||
@@ -8,7 +8,9 @@ import PrivateRoute from 'components/PrivateRoute';
|
||||
import Authentication from 'components/Authentication';
|
||||
import Dashboard from 'components/Dashboard/Dashboard';
|
||||
import { isAuthenticated } from 'store/authentication/authentication.reducer'
|
||||
import Progress from 'components/NProgress/Progress';
|
||||
import { ReactQueryConfigProvider } from 'react-query';
|
||||
import { ReactQueryDevtools } from "react-query-devtools";
|
||||
|
||||
import messages from 'lang/en';
|
||||
import 'style/App.scss';
|
||||
|
||||
@@ -22,13 +24,22 @@ function App({
|
||||
console.log(`new location via ${action}`, location);
|
||||
});
|
||||
|
||||
const queryConfig = {
|
||||
refetchAllOnWindowFocus: false,
|
||||
cacheTime: 10000,
|
||||
staleTime: 10000,
|
||||
};
|
||||
return (
|
||||
<IntlProvider locale={locale} messages={messages}>
|
||||
<div className="App">
|
||||
<Router history={history}>
|
||||
<Authentication isAuthenticated={isAuthorized} />
|
||||
<PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} />
|
||||
</Router>
|
||||
<ReactQueryConfigProvider config={queryConfig}>
|
||||
<Router history={history}>
|
||||
<Authentication isAuthenticated={isAuthorized} />
|
||||
<PrivateRoute isAuthenticated={isAuthorized} component={Dashboard} />
|
||||
</Router>
|
||||
|
||||
<ReactQueryDevtools />
|
||||
</ReactQueryConfigProvider>
|
||||
</div>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Redirect, Route, Switch, Link } from 'react-router-dom';
|
||||
import BodyClassName from 'react-body-classname';
|
||||
import authenticationRoutes from 'routes/authentication';
|
||||
|
||||
export default function({ isAuthenticated =false, ...rest }) {
|
||||
export default function AuthenticationWrapper({ isAuthenticated =false, ...rest }) {
|
||||
const to = {pathname: '/dashboard/homepage'};
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import DashboardContent from 'components/Dashboard/DashboardContent';
|
||||
import DialogsContainer from 'components/DialogsContainer';
|
||||
import PreferencesContent from 'components/Preferences/PreferencesContent';
|
||||
import PreferencesSidebar from 'components/Preferences/PreferencesSidebar';
|
||||
import Search from 'containers/Dashboard/GeneralSearch/Search';
|
||||
import Search from 'containers/GeneralSearch/Search';
|
||||
|
||||
export default function Dashboard() {
|
||||
return (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import AccountFormDialog from 'containers/Dashboard/Dialogs/AccountFormDialog';
|
||||
import UserFormDialog from 'containers/Dashboard/Dialogs/UserFormDialog';
|
||||
import ItemCategoryDialog from 'containers/Dashboard/Dialogs/ItemCategoryDialog';
|
||||
import CurrencyDialog from 'containers/Dashboard/Dialogs/CurrencyDialog';
|
||||
import InviteUserDialog from 'containers/Dashboard/Dialogs/InviteUserDialog';
|
||||
import AccountFormDialog from 'containers/Dialogs/AccountFormDialog';
|
||||
import UserFormDialog from 'containers/Dialogs/UserFormDialog';
|
||||
import ItemCategoryDialog from 'containers/Dialogs/ItemCategoryDialog';
|
||||
import CurrencyDialog from 'containers/Dialogs/CurrencyDialog';
|
||||
import InviteUserDialog from 'containers/Dialogs/InviteUserDialog';
|
||||
|
||||
export default function DialogsContainer() {
|
||||
return (
|
||||
|
||||
@@ -1,457 +0,0 @@
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import * as Yup from 'yup';
|
||||
import { useFormik } from 'formik';
|
||||
import {
|
||||
FormGroup,
|
||||
MenuItem,
|
||||
Intent,
|
||||
InputGroup,
|
||||
HTMLSelect,
|
||||
Button,
|
||||
Classes,
|
||||
Checkbox,
|
||||
} from '@blueprintjs/core';
|
||||
import { Row, Col } from 'react-grid-system';
|
||||
import { Select } from '@blueprintjs/select';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
import AccountsConnect from 'connectors/Accounts.connector';
|
||||
import ItemsConnect from 'connectors/Items.connect';
|
||||
import {compose} from 'utils';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import classNames from 'classnames';
|
||||
import Icon from 'components/Icon';
|
||||
import ItemCategoryConnect from 'connectors/ItemsCategory.connect';
|
||||
import MoneyInputGroup from 'components/MoneyInputGroup';
|
||||
import {useHistory} from 'react-router-dom';
|
||||
import Dragzone from 'components/Dragzone';
|
||||
import MediaConnect from 'connectors/Media.connect';
|
||||
import useMedia from 'hooks/useMedia';
|
||||
|
||||
const ItemForm = ({
|
||||
requestSubmitItem,
|
||||
|
||||
accounts,
|
||||
categories,
|
||||
|
||||
requestSubmitMedia,
|
||||
requestDeleteMedia,
|
||||
}) => {
|
||||
const [selectedAccounts, setSelectedAccounts] = useState({});
|
||||
const history = useHistory();
|
||||
|
||||
const {
|
||||
files,
|
||||
setFiles,
|
||||
saveMedia,
|
||||
deletedFiles,
|
||||
setDeletedFiles,
|
||||
deleteMedia,
|
||||
} = useMedia({
|
||||
saveCallback: requestSubmitMedia,
|
||||
deleteCallback: requestDeleteMedia,
|
||||
})
|
||||
|
||||
const ItemTypeDisplay = useMemo(() => ([
|
||||
{ value: null, label: 'Select Item Type' },
|
||||
{ value: 'service', label: 'Service' },
|
||||
{ value: 'inventory', label: 'Inventory' },
|
||||
{ value: 'non-inventory', label: 'Non-Inventory' }
|
||||
]), []);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
active: Yup.boolean(),
|
||||
name: Yup.string().required(),
|
||||
type: Yup.string().trim().required(),
|
||||
sku: Yup.string().trim(),
|
||||
cost_price: Yup.number(),
|
||||
sell_price: Yup.number(),
|
||||
cost_account_id: Yup.number().required(),
|
||||
sell_account_id: Yup.number().required(),
|
||||
inventory_account_id: Yup.number().when('type', {
|
||||
is: (value) => value === 'inventory',
|
||||
then: Yup.number().required(),
|
||||
otherwise: Yup.number().nullable(),
|
||||
}),
|
||||
category_id: Yup.number().nullable(),
|
||||
stock: Yup.string() || Yup.boolean()
|
||||
});
|
||||
|
||||
const initialValues = useMemo(() => ({
|
||||
active: true,
|
||||
name: '',
|
||||
type: '',
|
||||
sku: '',
|
||||
cost_price: 0,
|
||||
sell_price: 0,
|
||||
cost_account_id: null,
|
||||
sell_account_id: null,
|
||||
inventory_account_id: null,
|
||||
category_id: null,
|
||||
note: '',
|
||||
}), []);
|
||||
|
||||
const {
|
||||
getFieldProps,
|
||||
setFieldValue,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
handleSubmit,
|
||||
} = useFormik({
|
||||
enableReinitialize: true,
|
||||
validationSchema: validationSchema,
|
||||
initialValues: {
|
||||
...initialValues,
|
||||
},
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
const saveItem = (mediaIds) => {
|
||||
const formValues = { ...values, media_ids: mediaIds };
|
||||
|
||||
return requestSubmitItem(formValues).then((response) => {
|
||||
AppToaster.show({
|
||||
message: 'The_Items_has_been_submit'
|
||||
});
|
||||
setSubmitting(false);
|
||||
history.push('/dashboard/items');
|
||||
})
|
||||
.catch((error) => {
|
||||
setSubmitting(false);
|
||||
});
|
||||
};
|
||||
|
||||
Promise.all([
|
||||
saveMedia(),
|
||||
deleteMedia(),
|
||||
]).then(([savedMediaResponses]) => {
|
||||
const mediaIds = savedMediaResponses.map(res => res.data.media.id);
|
||||
return saveItem(mediaIds);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const accountItem = useCallback((item, { handleClick }) => (
|
||||
<MenuItem key={item.id} text={item.name} label={item.code} onClick={handleClick} />
|
||||
), []);
|
||||
|
||||
// Filter Account Items
|
||||
const filterAccounts = (query, account, _index, exactMatch) => {
|
||||
const normalizedTitle = account.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else {
|
||||
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
const onItemAccountSelect = useCallback((filedName) => {
|
||||
return (account) => {
|
||||
setSelectedAccounts({
|
||||
...selectedAccounts,
|
||||
[filedName]: account
|
||||
});
|
||||
setFieldValue(filedName, account.id);
|
||||
};
|
||||
}, [setFieldValue, selectedAccounts]);
|
||||
|
||||
const categoryItem = useCallback((item, { handleClick }) => (
|
||||
<MenuItem text={item.name} onClick={handleClick} />
|
||||
), []);
|
||||
|
||||
const getSelectedAccountLabel = useCallback((fieldName, defaultLabel) => {
|
||||
return typeof selectedAccounts[fieldName] !== 'undefined'
|
||||
? selectedAccounts[fieldName].name : defaultLabel;
|
||||
}, [selectedAccounts]);
|
||||
|
||||
const requiredSpan = useMemo(() => (<span class="required">*</span>), []);
|
||||
const infoIcon = useMemo(() => (<Icon icon="info-circle" iconSize={12} />), []);
|
||||
|
||||
const handleMoneyInputChange = (fieldKey) => (e, value) => {
|
||||
setFieldValue(fieldKey, value);
|
||||
};
|
||||
|
||||
const initialAttachmentFiles = useMemo(() => {
|
||||
return [];
|
||||
}, []);
|
||||
|
||||
const handleDropFiles = useCallback((_files) => {
|
||||
setFiles(_files.filter((file) => file.uploaded === false));
|
||||
}, []);
|
||||
|
||||
const handleDeleteFile = useCallback((_deletedFiles) => {
|
||||
_deletedFiles.forEach((deletedFile) => {
|
||||
if (deletedFile.uploaded && deletedFile.metadata.id) {
|
||||
setDeletedFiles([
|
||||
...deletedFiles, deletedFile.metadata.id,
|
||||
]);
|
||||
}
|
||||
});
|
||||
}, [setDeletedFiles, deletedFiles]);
|
||||
|
||||
return (
|
||||
<div class='item-form'>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div class="item-form__primary-section">
|
||||
<Row>
|
||||
<Col xs={7}>
|
||||
<FormGroup
|
||||
medium={true}
|
||||
label={'Item Type'}
|
||||
labelInfo={requiredSpan}
|
||||
className={'form-group--item-type'}
|
||||
intent={(errors.type && touched.type) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="type" />}
|
||||
inline={true}
|
||||
>
|
||||
<HTMLSelect
|
||||
fill={true}
|
||||
options={ItemTypeDisplay}
|
||||
{...getFieldProps('type')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'Item Name'}
|
||||
labelInfo={requiredSpan}
|
||||
className={'form-group--item-name'}
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="name" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
{...getFieldProps('name')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'SKU'}
|
||||
labelInfo={infoIcon}
|
||||
className={'form-group--item-sku'}
|
||||
intent={(errors.sku && touched.sku) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="sku" />}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
intent={(errors.sku && touched.sku) && Intent.DANGER}
|
||||
{...getFieldProps('sku')}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'Category'}
|
||||
labelInfo={infoIcon}
|
||||
inline={true}
|
||||
intent={(errors.category_id && touched.category_id) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="category" />}
|
||||
className={classNames(
|
||||
'form-group--select-list',
|
||||
'form-group--category',
|
||||
Classes.FILL,
|
||||
)}
|
||||
>
|
||||
<Select
|
||||
items={categories}
|
||||
itemRenderer={categoryItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('category_id')}
|
||||
>
|
||||
<Button
|
||||
fill={true}
|
||||
rightIcon='caret-down'
|
||||
text={getSelectedAccountLabel('category_id', 'Select category')}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={' '}
|
||||
inline={true}
|
||||
className={'form-group--active'}
|
||||
>
|
||||
<Checkbox
|
||||
inline={true}
|
||||
label={'Active'}
|
||||
defaultChecked={values.active}
|
||||
{...getFieldProps('active')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col xs={3}>
|
||||
<Dragzone
|
||||
initialFiles={initialAttachmentFiles}
|
||||
onDrop={handleDropFiles}
|
||||
onDeleteFile={handleDeleteFile}
|
||||
hint={'Attachments: Maxiumum size: 20MB'}
|
||||
className={'mt2'} />
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<Row gutterWidth={16} className={'item-form__accounts-section'}>
|
||||
<Col width={404}>
|
||||
<h4>Purchase Information</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Selling Price'}
|
||||
className={'form-group--item-selling-price'}
|
||||
intent={(errors.selling_price && touched.selling_price) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="selling_price" />}
|
||||
inline={true}
|
||||
>
|
||||
<MoneyInputGroup
|
||||
value={values.selling_price}
|
||||
prefix={'$'}
|
||||
onChange={handleMoneyInputChange('selling_price')}
|
||||
inputGroupProps={{
|
||||
medium: true,
|
||||
intent: (errors.selling_price && touched.selling_price) && Intent.DANGER,
|
||||
}} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'Account'}
|
||||
labelInfo={infoIcon}
|
||||
inline={true}
|
||||
intent={(errors.sell_account_id && touched.sell_account_id) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="sell_account_id" />}
|
||||
className={classNames(
|
||||
'form-group--sell-account', 'form-group--select-list',
|
||||
Classes.FILL)}
|
||||
>
|
||||
<Select
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('sell_account_id')}
|
||||
>
|
||||
<Button
|
||||
fill={true}
|
||||
rightIcon='caret-down'
|
||||
text={getSelectedAccountLabel('sell_account_id', 'Select account')}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col width={404}>
|
||||
<h4>
|
||||
Sales Information
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Cost Price'}
|
||||
className={'form-group--item-cost-price'}
|
||||
intent={(errors.cost_price && touched.cost_price) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="cost_price" />}
|
||||
inline={true}
|
||||
>
|
||||
<MoneyInputGroup
|
||||
value={values.cost_price}
|
||||
prefix={'$'}
|
||||
onChange={handleMoneyInputChange('cost_price')}
|
||||
inputGroupProps={{
|
||||
medium: true,
|
||||
intent: (errors.cost_price && touched.cost_price) && Intent.DANGER,
|
||||
}} />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'Account'}
|
||||
labelInfo={infoIcon}
|
||||
inline={true}
|
||||
intent={(errors.cost_account_id && touched.cost_account_id) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="cost_account_id" />}
|
||||
className={classNames(
|
||||
'form-group--cost-account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL)}
|
||||
>
|
||||
<Select
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('cost_account_id')}
|
||||
>
|
||||
<Button
|
||||
fill={true}
|
||||
rightIcon='caret-down'
|
||||
text={getSelectedAccountLabel('cost_account_id', 'Select account')}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row className={'item-form__accounts-section mt2'}>
|
||||
<Col width={404}>
|
||||
<h4>
|
||||
Inventory Information
|
||||
</h4>
|
||||
|
||||
<FormGroup
|
||||
label={'Inventory Account'}
|
||||
inline={true}
|
||||
intent={(errors.inventory_account_id && touched.inventory_account_id) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name="inventory_account_id" />}
|
||||
className={classNames(
|
||||
'form-group--item-inventory_account',
|
||||
'form-group--select-list',
|
||||
Classes.FILL)}
|
||||
>
|
||||
<Select
|
||||
items={accounts}
|
||||
itemRenderer={accountItem}
|
||||
itemPredicate={filterAccounts}
|
||||
popoverProps={{ minimal: true }}
|
||||
onItemSelect={onItemAccountSelect('inventory_account_id')}
|
||||
>
|
||||
<Button
|
||||
fill={true}
|
||||
rightIcon='caret-down'
|
||||
text={getSelectedAccountLabel('inventory_account_id','Select account')}
|
||||
/>
|
||||
</Select>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={'Opening Stock'}
|
||||
className={'form-group--item-stock'}
|
||||
// intent={errors.cost_price && Intent.DANGER}
|
||||
// helperText={formik.errors.stock && formik.errors.stock}
|
||||
inline={true}
|
||||
>
|
||||
<InputGroup
|
||||
medium={true}
|
||||
intent={errors.stock && Intent.DANGER}
|
||||
{...getFieldProps('stock')}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<div class='form__floating-footer'>
|
||||
<Button intent={Intent.PRIMARY} type='submit'>
|
||||
Save
|
||||
</Button>
|
||||
|
||||
<Button className={'ml1'}>Save as Draft</Button>
|
||||
<Button className={'ml1'} onClick={'handleClose'}>Close</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
AccountsConnect,
|
||||
ItemsConnect,
|
||||
ItemCategoryConnect,
|
||||
MediaConnect,
|
||||
)(ItemForm);
|
||||
@@ -1,13 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Button, AnchorButton, Classes, Icon } from '@blueprintjs/core';
|
||||
|
||||
const ItemsActionsBar = () => {
|
||||
|
||||
return (
|
||||
<div className='dashob'>
|
||||
ItemsActionsBar 22
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ItemsActionsBar;
|
||||
@@ -1,108 +0,0 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import ItemsCategoryConnect from 'connectors/ItemsCategory.connect';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import LoadingIndicator from 'components/LoadingIndicator';
|
||||
import { compose } from 'utils';
|
||||
import DataTable from 'components/DataTable';
|
||||
import {
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
|
||||
const ItemsCategoryList = ({
|
||||
categories,
|
||||
onFetchData,
|
||||
onDeleteCategory,
|
||||
onEditCategory,
|
||||
openDialog,
|
||||
count,
|
||||
onSelectedRowsChange,
|
||||
}) => {
|
||||
const handelEditCategory = (category) => () => {
|
||||
openDialog('item-form', { action: 'edit', id: category.id });
|
||||
onEditCategory(category.id);
|
||||
};
|
||||
|
||||
const handleDeleteCategory = (category) => () => {
|
||||
onDeleteCategory(category);
|
||||
};
|
||||
|
||||
const actionMenuList = (category) => (
|
||||
<Menu>
|
||||
<MenuItem text='Edit Category' onClick={handelEditCategory(category)} />
|
||||
<MenuItem
|
||||
text='Delete Category'
|
||||
onClick={handleDeleteCategory(category)}
|
||||
/>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'name',
|
||||
Header: 'Category Name',
|
||||
accessor: 'name',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
Header: 'Description',
|
||||
accessor: 'description',
|
||||
className: 'description',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
id: 'count',
|
||||
Header: 'Count',
|
||||
accessor: (r) => r.count || '',
|
||||
className: 'count',
|
||||
width: 50,
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon='ellipsis-h' />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: false
|
||||
},
|
||||
], [actionMenuList]);
|
||||
|
||||
const handelFetchData = useCallback(() => {
|
||||
onFetchData && onFetchData();
|
||||
}, []);
|
||||
|
||||
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||
}, [onSelectedRowsChange]);
|
||||
|
||||
return (
|
||||
<LoadingIndicator spinnerSize={30}>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={Object.values(categories)}
|
||||
onFetchData={handelFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
expandable={true}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
</LoadingIndicator>
|
||||
);
|
||||
};
|
||||
|
||||
export default compose(
|
||||
DialogConnect,
|
||||
ItemsCategoryConnect,
|
||||
)(ItemsCategoryList);
|
||||
@@ -1,94 +0,0 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
FormGroup,
|
||||
MenuItem,
|
||||
Intent,
|
||||
InputGroup,
|
||||
Position,
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { DateInput, TimePrecision } from "@blueprintjs/datetime";
|
||||
import {
|
||||
GridComponent,
|
||||
ColumnsDirective,
|
||||
ColumnDirective,
|
||||
Inject,
|
||||
Sort,
|
||||
} from '@syncfusion/ej2-react-grids';
|
||||
import {momentFormatter} from 'utils';
|
||||
|
||||
export default function MakeJournalEntry({
|
||||
accounts,
|
||||
currencies,
|
||||
}) {
|
||||
|
||||
const handleDateChange = () => {
|
||||
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
headerText: 'Account',
|
||||
},
|
||||
{
|
||||
headerText: 'Description',
|
||||
},
|
||||
{
|
||||
headerText: 'Account',
|
||||
},
|
||||
{
|
||||
headerText: 'Debit',
|
||||
},
|
||||
{
|
||||
headerText: 'Credit',
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div class="make-journal-entry">
|
||||
<div class="make-journal-entry__details">
|
||||
<FormGroup
|
||||
label={'Date'}
|
||||
inline={true}>
|
||||
|
||||
<DateInput
|
||||
{...momentFormatter('MM/DD/YYYY')}
|
||||
defaultValue={new Date()}
|
||||
onChange={handleDateChange}
|
||||
popoverProps={{ position: Position.BOTTOM }}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<GridComponent>
|
||||
<ColumnsDirective>
|
||||
{columns.map((column) => {
|
||||
return (<ColumnDirective
|
||||
field={column.field}
|
||||
headerText={column.headerText}
|
||||
template={column.template}
|
||||
allowSorting={true}
|
||||
customAttributes={column.customAttributes}
|
||||
/>);
|
||||
})}
|
||||
</ColumnsDirective>
|
||||
</GridComponent>
|
||||
|
||||
<div class="form__floating-footer">
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button intent={Intent.PRIMARY} type="submit">
|
||||
{ 'Save and Publish' }
|
||||
</Button>
|
||||
|
||||
<Button intent={Intent.PRIMARY} type="submit">
|
||||
{ 'Save as Draft' }
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import Icon from 'components/Icon';
|
||||
import {
|
||||
Button,
|
||||
NavbarGroup,
|
||||
Classes,
|
||||
NavbarDivider,
|
||||
MenuItem,
|
||||
Menu,
|
||||
Popover,
|
||||
PopoverInteractionKind,
|
||||
Position,
|
||||
Intent
|
||||
} from '@blueprintjs/core';
|
||||
import classNames from 'classnames';
|
||||
import { useRouteMatch, useHistory } from 'react-router-dom';
|
||||
import DashboardActionsBar from 'components/Dashboard/DashboardActionsBar';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import { compose } from 'utils';
|
||||
import FilterDropdown from 'components/FilterDropdown';
|
||||
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
|
||||
import ResourceConnect from 'connectors/Resource.connector';
|
||||
|
||||
function ManualJournalActionsBar({
|
||||
views,
|
||||
getResourceFields,
|
||||
addManualJournalsTableQueries,
|
||||
onFilterChanged,
|
||||
selectedRows,
|
||||
onBulkDelete
|
||||
}) {
|
||||
const { path } = useRouteMatch();
|
||||
const history = useHistory();
|
||||
|
||||
const manualJournalFields = getResourceFields('manual_journals');
|
||||
const viewsMenuItems = views.map(view => {
|
||||
return (
|
||||
<MenuItem href={`${path}/${view.id}/custom_view`} text={view.name} />
|
||||
);
|
||||
});
|
||||
|
||||
const onClickNewManualJournal = useCallback(() => {
|
||||
history.push('/dashboard/accounting/make-journal-entry');
|
||||
}, [history]);
|
||||
|
||||
const filterDropdown = FilterDropdown({
|
||||
fields: manualJournalFields,
|
||||
onFilterChange: filterConditions => {
|
||||
addManualJournalsTableQueries({
|
||||
filter_roles: filterConditions || ''
|
||||
});
|
||||
onFilterChanged && onFilterChanged(filterConditions);
|
||||
}
|
||||
});
|
||||
const hasSelectedRows = useMemo(() => selectedRows.length > 0, [selectedRows]);
|
||||
|
||||
// Handle delete button click.
|
||||
const handleBulkDelete = useCallback(() => {
|
||||
onBulkDelete && onBulkDelete(selectedRows.map(r => r.id));
|
||||
}, [onBulkDelete, selectedRows]);
|
||||
|
||||
return (
|
||||
<DashboardActionsBar>
|
||||
<NavbarGroup>
|
||||
<Popover
|
||||
content={<Menu>{viewsMenuItems}</Menu>}
|
||||
minimal={true}
|
||||
interactionKind={PopoverInteractionKind.HOVER}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--table-views')}
|
||||
icon={<Icon icon='table' />}
|
||||
text='Table Views'
|
||||
rightIcon={'caret-down'}
|
||||
/>
|
||||
</Popover>
|
||||
<NavbarDivider />
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='plus' />}
|
||||
text='New Journal'
|
||||
onClick={onClickNewManualJournal}
|
||||
/>
|
||||
<Popover
|
||||
content={filterDropdown}
|
||||
interactionKind={PopoverInteractionKind.CLICK}
|
||||
position={Position.BOTTOM_LEFT}
|
||||
>
|
||||
<Button
|
||||
className={classNames(Classes.MINIMAL, 'button--filter')}
|
||||
text='Filter'
|
||||
icon={<Icon icon='filter' />}
|
||||
/>
|
||||
</Popover>
|
||||
|
||||
{(hasSelectedRows) && (
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='trash' iconSize={15} />}
|
||||
text='Delete'
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleBulkDelete}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-import' />}
|
||||
text='Import'
|
||||
/>
|
||||
<Button
|
||||
className={Classes.MINIMAL}
|
||||
icon={<Icon icon='file-export' />}
|
||||
text='Export'
|
||||
/>
|
||||
</NavbarGroup>
|
||||
</DashboardActionsBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
DialogConnect,
|
||||
ManualJournalsConnect,
|
||||
ResourceConnect
|
||||
)(ManualJournalActionsBar);
|
||||
@@ -1,193 +0,0 @@
|
||||
import React, { useEffect, useCallback, useState, useMemo } from 'react';
|
||||
import {
|
||||
Intent,
|
||||
Button,
|
||||
Popover,
|
||||
Menu,
|
||||
MenuItem,
|
||||
MenuDivider,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { compose } from 'utils';
|
||||
import moment from 'moment';
|
||||
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
|
||||
import DialogConnect from 'connectors/Dialog.connector';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import ViewConnect from 'connectors/View.connector';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import DataTable from 'components/DataTable';
|
||||
import Money from 'components/Money';
|
||||
|
||||
function ManualJournalsDataTable({
|
||||
manualJournals,
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
getViewItem,
|
||||
setTopbarEditView,
|
||||
manualJournalsLoading,
|
||||
onFetchData,
|
||||
onEditJournal,
|
||||
onDeleteJournal,
|
||||
onPublishJournal,
|
||||
onSelectedRowsChange,
|
||||
}) {
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
const [initialMount, setInitialMount] = useState(false);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
if (!manualJournalsLoading) {
|
||||
setInitialMount(true);
|
||||
}
|
||||
}, [manualJournalsLoading, setInitialMount]);
|
||||
|
||||
useEffect(() => {
|
||||
const viewMeta = getViewItem(customViewId);
|
||||
|
||||
if (customViewId) {
|
||||
changeCurrentView(customViewId);
|
||||
setTopbarEditView(customViewId);
|
||||
}
|
||||
changePageSubtitle(customViewId && viewMeta ? viewMeta.name : '');
|
||||
}, [
|
||||
customViewId,
|
||||
changeCurrentView,
|
||||
changePageSubtitle,
|
||||
setTopbarEditView,
|
||||
getViewItem,
|
||||
]);
|
||||
|
||||
const handlePublishJournal = useCallback((journal) => () => {
|
||||
onPublishJournal && onPublishJournal(journal);
|
||||
}, [onPublishJournal]);
|
||||
|
||||
const handleEditJournal = useCallback((journal) => () => {
|
||||
onEditJournal && onEditJournal(journal);
|
||||
}, [onEditJournal]);
|
||||
|
||||
const handleDeleteJournal = useCallback((journal) => () => {
|
||||
onDeleteJournal && onDeleteJournal(journal);
|
||||
}, [onDeleteJournal]);
|
||||
|
||||
const actionMenuList = (journal) => (
|
||||
<Menu>
|
||||
<MenuItem text='View Details' />
|
||||
<MenuDivider />
|
||||
{!journal.status && (
|
||||
<MenuItem
|
||||
text="Publish Journal"
|
||||
onClick={handlePublishJournal(journal)} />
|
||||
)}
|
||||
<MenuItem
|
||||
text='Edit Journal'
|
||||
onClick={handleEditJournal(journal)} />
|
||||
<MenuItem
|
||||
text='Delete Journal'
|
||||
intent={Intent.DANGER}
|
||||
onClick={handleDeleteJournal(journal)} />
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
id: 'date',
|
||||
Header: 'Date',
|
||||
accessor: r => moment().format('YYYY-MM-DD'),
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
className: 'date',
|
||||
},
|
||||
{
|
||||
id: 'amount',
|
||||
Header: 'Amount',
|
||||
accessor: r => (<Money amount={r.amount} currency={'USD'} />),
|
||||
disableResizing: true,
|
||||
className: 'amount',
|
||||
},
|
||||
{
|
||||
id: 'journal_number',
|
||||
Header: 'Journal No.',
|
||||
accessor: 'journal_number',
|
||||
disableResizing: true,
|
||||
className: 'journal_number',
|
||||
},
|
||||
{
|
||||
id: 'status',
|
||||
Header: 'Status',
|
||||
accessor: (r) => {
|
||||
return r.status ? 'Published' : 'Draft';
|
||||
},
|
||||
disableResizing: true,
|
||||
width: 100,
|
||||
className: 'status',
|
||||
},
|
||||
{
|
||||
id: 'note',
|
||||
Header: 'Note',
|
||||
accessor: r => (<Icon icon={'file-alt'} iconSize={16} />),
|
||||
disableResizing: true,
|
||||
disableSorting: true,
|
||||
width: 100,
|
||||
className: 'note',
|
||||
},
|
||||
{
|
||||
id: 'transaction_type',
|
||||
Header: 'Transaction type ',
|
||||
accessor: 'transaction_type',
|
||||
width: 100,
|
||||
className: 'transaction_type',
|
||||
},
|
||||
{
|
||||
id: 'created_at',
|
||||
Header: 'Created At',
|
||||
accessor: r => moment().format('YYYY-MM-DD'),
|
||||
disableResizing: true,
|
||||
width: 150,
|
||||
className: 'created_at',
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
Header: '',
|
||||
Cell: ({ cell }) => (
|
||||
<Popover
|
||||
content={actionMenuList(cell.row.original)}
|
||||
position={Position.RIGHT_BOTTOM}
|
||||
>
|
||||
<Button icon={<Icon icon='ellipsis-h' />} />
|
||||
</Popover>
|
||||
),
|
||||
className: 'actions',
|
||||
width: 50,
|
||||
disableResizing: true,
|
||||
},
|
||||
], []);
|
||||
|
||||
const handleDataTableFetchData = useCallback((...args) => {
|
||||
onFetchData && onFetchData(...args);
|
||||
}, [onFetchData]);
|
||||
|
||||
const handleSelectedRowsChange = useCallback((selectedRows) => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedRows.map(s => s.original));
|
||||
}, [onSelectedRowsChange]);
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={manualJournals}
|
||||
onFetchData={handleDataTableFetchData}
|
||||
manualSortBy={true}
|
||||
selectionColumn={true}
|
||||
noInitialFetch={true}
|
||||
loading={manualJournalsLoading && !initialMount}
|
||||
onSelectedRowsChange={handleSelectedRowsChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
DialogConnect,
|
||||
DashboardConnect,
|
||||
ViewConnect,
|
||||
ManualJournalsConnect,
|
||||
)(ManualJournalsDataTable);
|
||||
@@ -1,96 +0,0 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { useHistory } from 'react-router';
|
||||
import {
|
||||
Alignment,
|
||||
Navbar,
|
||||
NavbarGroup,
|
||||
Tabs,
|
||||
Tab,
|
||||
Button,
|
||||
} from '@blueprintjs/core';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import Icon from 'components/Icon';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { compose } from 'utils';
|
||||
import ManualJournalsConnect from 'connectors/ManualJournals.connect';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
|
||||
function ManualJournalsViewTabs({
|
||||
views,
|
||||
setTopbarEditView,
|
||||
customViewChanged,
|
||||
addManualJournalsTableQueries,
|
||||
onViewChanged,
|
||||
}) {
|
||||
const history = useHistory();
|
||||
const { custom_view_id: customViewId } = useParams();
|
||||
|
||||
const handleClickNewView = () => {
|
||||
setTopbarEditView(null);
|
||||
history.push('/dashboard/custom_views/manual_journals/new');
|
||||
};
|
||||
const handleViewLinkClick = () => {
|
||||
setTopbarEditView(customViewId);
|
||||
};
|
||||
|
||||
useUpdateEffect(() => {
|
||||
customViewChanged && customViewChanged(customViewId);
|
||||
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: customViewId || null,
|
||||
});
|
||||
onViewChanged && onViewChanged(customViewId);
|
||||
}, [customViewId]);
|
||||
|
||||
useEffect(() => {
|
||||
addManualJournalsTableQueries({
|
||||
custom_view_id: customViewId,
|
||||
});
|
||||
}, [customViewId]);
|
||||
|
||||
const tabs = views.map((view) => {
|
||||
const baseUrl = '/dashboard/accounting/manual-journals';
|
||||
const link = (
|
||||
<Link
|
||||
to={`${baseUrl}/${view.id}/custom_view`}
|
||||
onClick={handleViewLinkClick}
|
||||
>
|
||||
{view.name}
|
||||
</Link>
|
||||
);
|
||||
return <Tab id={`custom_view_${view.id}`} title={link} />;
|
||||
});
|
||||
|
||||
return (
|
||||
<Navbar className='navbar--dashboard-views'>
|
||||
<NavbarGroup align={Alignment.LEFT}>
|
||||
<Tabs
|
||||
id='navbar'
|
||||
large={true}
|
||||
selectedTabId={`custom_view_${customViewId}`}
|
||||
className='tabs--dashboard-views'
|
||||
>
|
||||
<Tab
|
||||
id='all'
|
||||
title={
|
||||
<Link to={`/dashboard/accounting/manual-journals`}>All</Link>
|
||||
}
|
||||
/>
|
||||
{tabs}
|
||||
<Button
|
||||
className='button--new-view'
|
||||
icon={<Icon icon='plus' />}
|
||||
onClick={handleClickNewView}
|
||||
minimal={true}
|
||||
/>
|
||||
</Tabs>
|
||||
</NavbarGroup>
|
||||
</Navbar>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
ManualJournalsConnect,
|
||||
DashboardConnect
|
||||
)(ManualJournalsViewTabs);
|
||||
@@ -1,18 +1,16 @@
|
||||
|
||||
import React from 'react';
|
||||
import {connect} from 'react-redux';
|
||||
import Progress from './Progress';
|
||||
import {queryCache, useIsFetching} from 'react-query';
|
||||
|
||||
function AppProgress({
|
||||
isAnimating,
|
||||
|
||||
}) {
|
||||
const isFetching = useIsFetching();
|
||||
|
||||
return (
|
||||
<Progress isAnimating={isAnimating} />
|
||||
<Progress isAnimating={isFetching} />
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isAnimating: state.dashboard.requestsLoading > 0,
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(AppProgress);
|
||||
|
||||
export default AppProgress;
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
|
||||
import UsersActions from 'containers/Dashboard/Preferences/UsersActions';
|
||||
import CurrenciesActions from 'containers/Dashboard/Preferences/CurrenciesActions';
|
||||
import UsersActions from 'containers/Preferences/UsersActions';
|
||||
import CurrenciesActions from 'containers/Preferences/CurrenciesActions';
|
||||
|
||||
|
||||
export default function PreferencesTopbar() {
|
||||
|
||||
33
client/src/components/Utils/Choose.js
Normal file
33
client/src/components/Utils/Choose.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import If from './if';
|
||||
|
||||
const Choose = props => {
|
||||
let when = null;
|
||||
let otherwise = null;
|
||||
|
||||
React.Children.forEach(props.children, children => {
|
||||
if (children.props.condition === undefined) {
|
||||
otherwise = children;
|
||||
} else if (!when && children.props.condition === true) {
|
||||
when = children;
|
||||
}
|
||||
});
|
||||
|
||||
return when || otherwise;
|
||||
};
|
||||
|
||||
Choose.propTypes = {
|
||||
children: PropTypes.node
|
||||
};
|
||||
|
||||
Choose.When = If;
|
||||
|
||||
Choose.Otherwise = ({render, children}) => render ? render() : children;
|
||||
|
||||
Choose.Otherwise.propTypes = {
|
||||
children: PropTypes.node,
|
||||
render: PropTypes.func
|
||||
};
|
||||
|
||||
export default Choose;
|
||||
11
client/src/components/Utils/For.js
Normal file
11
client/src/components/Utils/For.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const For = ({render, of}) => of.map((item, index) => render(item, index));
|
||||
|
||||
For.propTypes = {
|
||||
of: PropTypes.array.isRequired,
|
||||
render: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default For;
|
||||
13
client/src/components/Utils/If.js
Normal file
13
client/src/components/Utils/If.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const If = props => props.condition
|
||||
? (props.render ? props.render() : props.children) : null;
|
||||
|
||||
If.propTypes = {
|
||||
condition: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
render: PropTypes.func
|
||||
};
|
||||
|
||||
export default If;
|
||||
@@ -1,398 +0,0 @@
|
||||
import React, {useState, useEffect, useCallback, useMemo} from 'react';
|
||||
import { useFormik } from "formik";
|
||||
import {useIntl} from 'react-intl';
|
||||
import {
|
||||
InputGroup,
|
||||
FormGroup,
|
||||
Intent,
|
||||
Button,
|
||||
MenuItem,
|
||||
Classes,
|
||||
HTMLSelect,
|
||||
Menu,
|
||||
H5,
|
||||
H6,
|
||||
} from "@blueprintjs/core";
|
||||
import {Row, Col} from 'react-grid-system';
|
||||
import { ReactSortable } from 'react-sortablejs';
|
||||
import * as Yup from 'yup';
|
||||
import {pick, get} from 'lodash';
|
||||
import Icon from 'components/Icon';
|
||||
import ViewFormConnect from 'connectors/ViewFormPage.connector';
|
||||
import {compose} from 'utils';
|
||||
import ErrorMessage from 'components/ErrorMessage';
|
||||
import DashboardConnect from 'connectors/Dashboard.connector';
|
||||
import ResourceConnect from 'connectors/Resource.connector';
|
||||
import AppToaster from 'components/AppToaster';
|
||||
|
||||
function ViewForm({
|
||||
resourceName,
|
||||
columns,
|
||||
fields,
|
||||
viewColumns,
|
||||
viewForm,
|
||||
viewFormColumns,
|
||||
submitView,
|
||||
editView,
|
||||
onDelete,
|
||||
getResourceField,
|
||||
getResourceColumn,
|
||||
}) {
|
||||
const intl = useIntl();
|
||||
|
||||
const [draggedColumns, setDraggedColumn] = useState([
|
||||
...(viewForm && viewForm.columns) ? viewForm.columns.map((column) => {
|
||||
return getResourceColumn(column.field_id);
|
||||
}) : []
|
||||
]);
|
||||
|
||||
const draggedColumnsIds = useMemo(() =>
|
||||
draggedColumns.map(c => c.id), [draggedColumns]);
|
||||
|
||||
const [availableColumns, setAvailableColumns] = useState([
|
||||
...(viewForm && viewForm.columns) ? columns.filter((column) =>
|
||||
draggedColumnsIds.indexOf(column.id) === -1
|
||||
) : columns,
|
||||
]);
|
||||
|
||||
const defaultViewRole = useMemo(() => ({
|
||||
field_key: '', comparator: '', value: '', index: 1,
|
||||
}), []);
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
resource_name: Yup.string().required(),
|
||||
name: Yup.string().required(),
|
||||
logic_expression: Yup.string().required(),
|
||||
roles: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
comparator: Yup.string().required(),
|
||||
value: Yup.string().required(),
|
||||
field_key: Yup.string().required(),
|
||||
index: Yup.number().required(),
|
||||
})
|
||||
),
|
||||
columns: Yup.array().of(
|
||||
Yup.object().shape({
|
||||
key: Yup.string().required(),
|
||||
index: Yup.string().required(),
|
||||
}),
|
||||
),
|
||||
});
|
||||
const initialEmptyForm = useMemo(() => ({
|
||||
resource_name: resourceName || '',
|
||||
name: '',
|
||||
logic_expression: '',
|
||||
roles: [
|
||||
defaultViewRole,
|
||||
],
|
||||
columns: [],
|
||||
}), [defaultViewRole, resourceName]);
|
||||
|
||||
const initialForm = useMemo(() =>
|
||||
({
|
||||
...initialEmptyForm,
|
||||
...viewForm ? {
|
||||
...viewForm,
|
||||
resource_name: viewForm.resource.name,
|
||||
} : {},
|
||||
}),
|
||||
[initialEmptyForm, viewForm]);
|
||||
|
||||
const {
|
||||
values,
|
||||
errors,
|
||||
touched,
|
||||
setFieldValue,
|
||||
getFieldProps,
|
||||
handleSubmit,
|
||||
isSubmitting,
|
||||
} = useFormik({
|
||||
enableReinitialize: true,
|
||||
validationSchema: validationSchema,
|
||||
initialValues: {
|
||||
...pick(initialForm, Object.keys(initialEmptyForm)),
|
||||
logic_expression: initialForm.roles_logic_expression || '',
|
||||
roles: [
|
||||
...initialForm.roles.map((role) => {
|
||||
return {
|
||||
...pick(role, Object.keys(defaultViewRole)),
|
||||
field_key: role.field ? role.field.key : '',
|
||||
};
|
||||
}),
|
||||
],
|
||||
},
|
||||
onSubmit: (values, { setSubmitting }) => {
|
||||
if (viewForm && viewForm.id) {
|
||||
editView(viewForm.id, values).then((response) => {
|
||||
AppToaster.show({
|
||||
message: 'the_view_has_been_edited'
|
||||
});
|
||||
setSubmitting(false);
|
||||
});
|
||||
} else {
|
||||
submitView(values).then((response) => {
|
||||
AppToaster.show({
|
||||
message: 'the_view_has_been_submit'
|
||||
});
|
||||
setSubmitting(false);
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue('columns',
|
||||
draggedColumns.map((column, index) => ({
|
||||
index, key: column.key,
|
||||
})));
|
||||
}, [setFieldValue, draggedColumns]);
|
||||
|
||||
const conditionalsItems = useMemo(() => ([
|
||||
{ value: 'and', label: 'AND' },
|
||||
{ value: 'or', label: 'OR' },
|
||||
]), []);
|
||||
|
||||
const whenConditionalsItems = useMemo(() => ([
|
||||
{ value: '', label: 'When' },
|
||||
]), []);
|
||||
|
||||
// Compatotors items.
|
||||
const compatatorsItems = useMemo(() => ([
|
||||
{value: '', label: 'Compatator'},
|
||||
{value: 'equals', label: 'Equals'},
|
||||
{value: 'not_equal', label: 'Not Equal'},
|
||||
{value: 'contain', label: 'Contain'},
|
||||
{value: 'not_contain', label: 'Not Contain'},
|
||||
]), []);
|
||||
|
||||
// Resource fields.
|
||||
const resourceFields = useMemo(() => ([
|
||||
{value: '', label: 'Select a field'},
|
||||
...fields.map((field) => ({ value: field.key, label: field.label_name, })),
|
||||
]), [fields]);
|
||||
|
||||
// Account item of select accounts field.
|
||||
const selectItem = (item, { handleClick, modifiers, query }) => {
|
||||
return (<MenuItem text={item.label} key={item.key} onClick={handleClick} />)
|
||||
};
|
||||
// Handle click new condition button.
|
||||
const onClickNewRole = useCallback(() => {
|
||||
setFieldValue('roles', [
|
||||
...values.roles,
|
||||
{
|
||||
...defaultViewRole,
|
||||
index: values.roles.length + 1,
|
||||
}
|
||||
]);
|
||||
}, [defaultViewRole, setFieldValue, values]);
|
||||
|
||||
// Handle click remove view role button.
|
||||
const onClickRemoveRole = useCallback((viewRole, index) => () => {
|
||||
const viewRoles = [...values.roles];
|
||||
|
||||
// Can't continue if view roles equals or less than 1.
|
||||
if (viewRoles.length <= 1) { return; }
|
||||
|
||||
viewRoles.splice(index, 1);
|
||||
viewRoles.map((role, i) => {
|
||||
role.index = i + 1;
|
||||
return role;
|
||||
});
|
||||
setFieldValue('roles', viewRoles);
|
||||
}, [values, setFieldValue]);
|
||||
|
||||
const onClickDeleteView = useCallback(() => {
|
||||
onDelete && onDelete(viewForm);
|
||||
}, [onDelete, viewForm]);
|
||||
|
||||
const hasError = (path) => get(errors, path) && get(touched, path);
|
||||
|
||||
console.log(errors, touched);
|
||||
|
||||
return (
|
||||
<div class="view-form">
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div class="view-form--name-section">
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'View Name'})}
|
||||
className={'form-group--name'}
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name={'name'} />}
|
||||
inline={true}
|
||||
fill={true}>
|
||||
|
||||
<InputGroup
|
||||
intent={(errors.name && touched.name) && Intent.DANGER}
|
||||
fill={true}
|
||||
{...getFieldProps('name')} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<H5 className="mb2">Define the conditionals</H5>
|
||||
|
||||
{values.roles.map((role, index) => (
|
||||
<Row class="view-form__role-conditional">
|
||||
<Col sm={2} class="flex">
|
||||
<div class="mr2 pt1 condition-number">{ index + 1 }</div>
|
||||
{(index === 0) ? (
|
||||
<HTMLSelect options={whenConditionalsItems} className={Classes.FILL} />
|
||||
) : (
|
||||
<HTMLSelect options={conditionalsItems} className={Classes.FILL} />
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Col sm={2}>
|
||||
<FormGroup
|
||||
intent={hasError(`roles[${index}].field_key`) && Intent.DANGER}>
|
||||
<HTMLSelect
|
||||
options={resourceFields}
|
||||
value={role.field_key}
|
||||
className={Classes.FILL}
|
||||
{...getFieldProps(`roles[${index}].field_key`)} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col sm={2}>
|
||||
<FormGroup
|
||||
intent={hasError(`roles[${index}].comparator`) && Intent.DANGER}>
|
||||
<HTMLSelect
|
||||
options={compatatorsItems}
|
||||
value={role.comparator}
|
||||
className={Classes.FILL}
|
||||
{...getFieldProps(`roles[${index}].comparator`)} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
|
||||
<Col sm={5} class="flex">
|
||||
<FormGroup
|
||||
intent={hasError(`roles[${index}].value`) && Intent.DANGER}>
|
||||
<InputGroup
|
||||
placeholder={intl.formatMessage({'id': 'value'})}
|
||||
{...getFieldProps(`roles[${index}].value`)} />
|
||||
</FormGroup>
|
||||
|
||||
<Button
|
||||
icon={<Icon icon="times-circle" iconSize={14} />}
|
||||
iconSize={14}
|
||||
className="ml2"
|
||||
minimal={true}
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickRemoveRole(role, index)} />
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
|
||||
<div className={'view-form__role-conditions-actions'}>
|
||||
<Button
|
||||
minimal={true}
|
||||
intent={Intent.PRIMARY}
|
||||
onClick={onClickNewRole}>
|
||||
New Conditional
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div class="view-form--logic-expression-section">
|
||||
<Row>
|
||||
<Col sm={8}>
|
||||
<FormGroup
|
||||
label={intl.formatMessage({'id': 'Logic Expression'})}
|
||||
className={'form-group--logic-expression'}
|
||||
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER}
|
||||
helperText={<ErrorMessage {...{errors, touched}} name='logic_expression' />}
|
||||
inline={true}
|
||||
fill={true}>
|
||||
|
||||
<InputGroup
|
||||
intent={(errors.logic_expression && touched.logic_expression) && Intent.DANGER}
|
||||
fill={true}
|
||||
{...getFieldProps('logic_expression')} />
|
||||
</FormGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<H5 className={'mb2'}>Columns Preferences</H5>
|
||||
|
||||
<div class="dragable-columns">
|
||||
<Row gutterWidth={14}>
|
||||
<Col sm={4} className="dragable-columns__column">
|
||||
<H6 className="dragable-columns__title">Available Columns</H6>
|
||||
|
||||
<InputGroup
|
||||
placeholder={intl.formatMessage({id: 'search'})}
|
||||
leftIcon="search" />
|
||||
|
||||
<div class="dragable-columns__items">
|
||||
<Menu>
|
||||
<ReactSortable
|
||||
list={availableColumns}
|
||||
setList={setAvailableColumns}
|
||||
group="shared-group-name">
|
||||
{availableColumns.map((field) => (
|
||||
<MenuItem key={field.id} text={field.label} />
|
||||
))}
|
||||
</ReactSortable>
|
||||
</Menu>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col sm={1}>
|
||||
<div class="dragable-columns__arrows">
|
||||
<div><Icon icon="arrow-circle-left" iconSize={30} color="#cecece" /></div>
|
||||
<div class="mt2"><Icon icon="arrow-circle-right" iconSize={30} color="#cecece" /></div>
|
||||
</div>
|
||||
</Col>
|
||||
|
||||
<Col sm={4} className="dragable-columns__column">
|
||||
<H6 className="dragable-columns__title">Selected Columns</H6>
|
||||
<InputGroup placeholder={intl.formatMessage({id: 'search'})} leftIcon="search" />
|
||||
|
||||
<div class="dragable-columns__items">
|
||||
<Menu>
|
||||
<ReactSortable
|
||||
list={draggedColumns}
|
||||
setList={setDraggedColumn}
|
||||
group="shared-group-name">
|
||||
{draggedColumns.map((field) => (
|
||||
<MenuItem key={field.id} text={field.label} />
|
||||
))}
|
||||
</ReactSortable>
|
||||
</Menu>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
<div class="form__floating-footer">
|
||||
<Button
|
||||
intent={Intent.PRIMARY}
|
||||
type="submit"
|
||||
disabled={isSubmitting}>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<Button intent={Intent.NONE} type="submit" className="ml1">Cancel</Button>
|
||||
|
||||
{ (viewForm && viewForm.id) && (
|
||||
<Button
|
||||
intent={Intent.DANGER}
|
||||
onClick={onClickDeleteView}
|
||||
className={"right mr2"}>
|
||||
Delete
|
||||
</Button>
|
||||
) }
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default compose(
|
||||
ViewFormConnect,
|
||||
DashboardConnect,
|
||||
ResourceConnect,
|
||||
)(ViewForm);
|
||||
9
client/src/components/index.js
Normal file
9
client/src/components/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import If from './Utils/If';
|
||||
// import Choose from './Utils/Choose';
|
||||
// import For from './Utils/For';
|
||||
|
||||
export {
|
||||
If,
|
||||
// Choose,
|
||||
// For,
|
||||
};
|
||||
Reference in New Issue
Block a user