WIP Version 0.0.1

This commit is contained in:
Ahmed Bouhuolia
2020-05-08 04:36:04 +02:00
parent bd7eb0eb76
commit 71cc561bb2
151 changed files with 1742 additions and 1081 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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>
);

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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>
)
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;

View File

@@ -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() {

View 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;

View 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;

View 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;

View File

@@ -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);

View File

@@ -0,0 +1,9 @@
import If from './Utils/If';
// import Choose from './Utils/Choose';
// import For from './Utils/For';
export {
If,
// Choose,
// For,
};