Config With Seacrh

This commit is contained in:
elforjani3
2020-04-11 15:00:22 +02:00
parent e8079dbde3
commit 940418c4e0
12 changed files with 284 additions and 149 deletions

View File

@@ -14,59 +14,79 @@ import {
import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs'; import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import DashboardTopbarUser from 'components/Dashboard/TopbarUser'; import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import Icon from 'components/Icon'; import Icon from 'components/Icon';
import Search from 'containers/Dashboard/GeneralSearch/Search';
function DashboardTopbar({ function DashboardTopbar({ pageTitle, pageSubtitle, editViewId }) {
pageTitle,
pageSubtitle,
editViewId,
}) {
const history = useHistory(); const history = useHistory();
const handlerClickEditView = () => { const handlerClickEditView = () => {
history.push(`/dashboard/custom_views/${editViewId}/edit`) history.push(`/dashboard/custom_views/${editViewId}/edit`);
} };
const maybleRenderPageSubtitle = pageSubtitle && (<h3>{ pageSubtitle }</h3>); const maybleRenderPageSubtitle = pageSubtitle && <h3>{pageSubtitle}</h3>;
const maybeRenderEditViewBtn = (pageSubtitle && editViewId) && ( const maybeRenderEditViewBtn = pageSubtitle && editViewId && (
<Button <Button
className={Classes.MINIMAL + ' button--view-edit'} className={Classes.MINIMAL + ' button--view-edit'}
icon={<Icon icon="pen" iconSize={13} />} icon={<Icon icon='pen' iconSize={13} />}
onClick={handlerClickEditView} /> onClick={handlerClickEditView}
/>
); );
return ( return (
<div class="dashboard__topbar"> <div class='dashboard__topbar'>
<div class="dashboard__topbar-left"> <div class='dashboard__topbar-left'>
<div class="dashboard__topbar-sidebar-toggle"> <div class='dashboard__topbar-sidebar-toggle'>
<Button> <Button>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" role="img" focusable="false"> <svg
xmlns='http://www.w3.org/2000/svg'
width='20'
height='20'
viewBox='0 0 20 20'
role='img'
focusable='false'
>
<title>Menu</title> <title>Menu</title>
<path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="5" stroke-width="2" d="M4 7h15M4 12h15M4 17h15"></path> <path
stroke='currentColor'
stroke-linecap='round'
stroke-miterlimit='5'
stroke-width='2'
d='M4 7h15M4 12h15M4 17h15'
></path>
</svg> </svg>
</Button> </Button>
</div> </div>
<div class="dashboard__title"> <div class='dashboard__title'>
<h1>{pageTitle}</h1> <h1>{pageTitle}</h1>
{maybleRenderPageSubtitle} {maybleRenderPageSubtitle}
{maybeRenderEditViewBtn} {maybeRenderEditViewBtn}
</div> </div>
<div class="dashboard__breadcrumbs"> <div class='dashboard__breadcrumbs'>
<DashboardBreadcrumbs /> <DashboardBreadcrumbs />
</div> </div>
</div> </div>
<div class="dashboard__topbar-right"> <div class='dashboard__topbar-right'>
<Navbar class="dashboard__topbar-navbar"> <Navbar class='dashboard__topbar-navbar'>
<NavbarGroup> <NavbarGroup>
<Button className={Classes.MINIMAL} icon="home" text="Search" /> {/* <Button className={Classes.MINIMAL} icon="home" text="Search" /> */}
<Button className={Classes.MINIMAL} icon="document" text="Filters" /> <Search className />
<Button className={Classes.MINIMAL} icon="document" text="Add order" /> <Button
<Button className={Classes.MINIMAL} icon="document" text="More" /> className={Classes.MINIMAL}
icon='document'
text='Filters'
/>
<Button
className={Classes.MINIMAL}
icon='document'
text='Add order'
/>
<Button className={Classes.MINIMAL} icon='document' text='More' />
</NavbarGroup> </NavbarGroup>
</Navbar> </Navbar>
<div class="dashboard__topbar-user"> <div class='dashboard__topbar-user'>
<DashboardTopbarUser /> <DashboardTopbarUser />
</div> </div>
</div> </div>

View File

@@ -1,60 +0,0 @@
import React, { useMemo } from 'react';
import {
Button,
Popover,
Menu,
MenuItem,
MenuDivider,
Position,
Checkbox
} from '@blueprintjs/core';
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Inject,
Sort
} from '@syncfusion/ej2-react-grids';
import LoadingIndicator from 'components/LoadingIndicator';
import DashboardConnect from 'connectors/Dashboard.connector';
import ItemFormDialogConnect from 'connectors/ItemFormDialog.connect';
import Icon from 'components/Icon';
import { useParams } from 'react-router-dom';
import { handleBooleanChange, compose } from 'utils';
import DataTable from 'components/DataTable';
const categoryList = ({ categories }) => {
const columns = [
{
field: 'name',
headerText: 'Name'
},
{
field: 'description',
headerText: 'Description'
}
];
return (
<LoadingIndicator>
<GridComponent
allowSorting={true}
dataSource={columns}
enableVirtualization={true}
>
<ColumnsDirective>
<ColumnDirective type='checkbox'></ColumnDirective>
{/* {columns.map(column => {
return (
<ColumnDirective
field={column.field}
headerText={column.headerText}
allowSorting={true}
/>
);
})} */}
</ColumnsDirective>
</GridComponent>
</LoadingIndicator>
);
};
export default compose(DashboardConnect, ItemFormDialogConnect)(categoryList);

View File

@@ -18,7 +18,6 @@ import { useUpdateEffect } from 'hooks';
function ManualJournalsViewTabs({ function ManualJournalsViewTabs({
views, views,
manualJournals,
setTopbarEditView, setTopbarEditView,
customViewChanged, customViewChanged,
addManualJournalsTableQueries, addManualJournalsTableQueries,
@@ -51,8 +50,6 @@ function ManualJournalsViewTabs({
}, [customViewId]); }, [customViewId]);
const tabs = views.map((view) => { const tabs = views.map((view) => {
//FIXME: dashboard/accounting/make-journal-entry
const baseUrl = '/dashboard/accounting/manual-journals'; const baseUrl = '/dashboard/accounting/manual-journals';
const link = ( const link = (
<Link <Link

View File

@@ -0,0 +1,13 @@
import { connect } from 'react-redux';
import t from 'store/types';
import { generalSearch } from 'store/search/search.actions';
export const mapStateToProps = (state, props) => ({
resultSearch: state.GeneralSearch,
});
export const mapDispatchToProps = (dispatch) => ({
generalSearch: (result) => dispatch(generalSearch(result)),
});
export default connect(mapStateToProps, mapDispatchToProps);

View File

@@ -0,0 +1,74 @@
import React, { useEffect, useState } from 'react';
import { Omnibar } from '@blueprintjs/select';
import { MenuItem, Button, Classes } from '@blueprintjs/core';
import { useAsync } from 'react-use';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import Icon from 'components/Icon';
import { compose } from 'utils';
import SearchConnect from 'connectors/Search.connect';
function Search({}) {
const [isOpen, setIsOpen] = useState(false);
const items = [
{ title: 'The Shawshank Redemption', year: 1994 },
{ title: 'The Godfather', year: 1972 },
{ title: 'The Godfather: Part II', year: 1974 },
];
const validationSchema = Yup.object().shape({
name: Yup.string().required(),
});
const formik = useFormik({
enableReinitialize: true,
validationSchema: validationSchema,
initialValues: {},
});
const renderSearch = (search, { handleClick, modifiers }) => (
<MenuItem
active={modifiers.active}
key={search.id}
text={search.name}
label={search.name}
onClick={handleClick}
/>
);
const filterSearch = (query, search, _index, exactMatch) => {
// const normalizedTitle = search.toLowerCase();
// const normalizedQuery = query.toLowerCase();
// if (exactMatch) {
// return normalizedTitle === normalizedQuery;
// } else {
// return `${search} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
// }
return search;
};
const handleClick = () => setIsOpen(true);
const handleClose = () => setIsOpen(false);
return (
<div>
<Button
className={Classes.MINIMAL}
icon='home'
text='Search'
onClick={handleClick}
/>
<Omnibar
isOpen={isOpen}
noResults={<MenuItem disabled={true} text='No results.' />}
onClose={handleClose}
resetOnSelect={true}
itemRenderer={renderSearch}
items={items}
// onItemSelect={}
itemListPredicate={filterSearch}
// style={{ position: 'absolute', canter: '50%' }}
/>
</div>
);
}
export default compose(SearchConnect)(Search);

View File

@@ -2,7 +2,7 @@ import ApiService from 'services/ApiService';
import t from 'store/types'; import t from 'store/types';
export const submitItemCategory = ({ form }) => { export const submitItemCategory = ({ form }) => {
return dispatch => { return (dispatch) => {
return ApiService.post('item_categories', { ...form }); return ApiService.post('item_categories', { ...form });
}; };
}; };
@@ -11,53 +11,53 @@ export const fetchItemCategories = () => {
return (dispatch, getState) => return (dispatch, getState) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.get('item_categories') ApiService.get('item_categories')
.then(response => { .then((response) => {
dispatch({ dispatch({
type: t.ITEMS_CATEGORY_LIST_SET, type: t.ITEMS_CATEGORY_LIST_SET,
categories: response.data.categories categories: response.data.categories,
}); });
resolve(response); resolve(response);
}) })
.catch(error => { .catch((error) => {
reject(error); reject(error);
}); });
}); });
}; };
export const editItemCategory = (id, form) => { export const editItemCategory = (id, form) => {
return dispatch => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.post(`item_categories/${id}`, form) ApiService.post(`item_categories/${id}`, form)
.then(response => { .then((response) => {
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS }); dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS });
resolve(response); resolve(response);
}) })
.catch(error => { .catch((error) => {
const { response } = error; const { response } = error;
const { data } = response; const { data } = response;
const { errors } = data; const { errors } = data;
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS }); dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS });
if (errors) { if (errors) {
dispatch({ type: t.CATEGORY_FORM_ERRORS, errors }); dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS, errors });
} }
reject(error); reject(error);
}); });
}); });
}; };
export const deleteItemCategory = id => { export const deleteItemCategory = (id) => {
return dispatch => return (dispatch) =>
new Promise((resolve, reject) => { new Promise((resolve, reject) => {
ApiService.delete(`item_categories/${id}`) ApiService.delete(`item_categories/${id}`)
.then(response => { .then((response) => {
dispatch({ dispatch({
type: t.CATEGORY_DELETE, type: t.CATEGORY_DELETE,
id id,
}); });
resolve(response); resolve(response);
}) })
.catch(error => { .catch((error) => {
reject(error); reject(error);
}); });
}); });

View File

@@ -14,6 +14,7 @@ import financialStatements from './financialStatement/financialStatements.reduce
import itemCategories from './itemCategories/itemsCategory.reducer'; import itemCategories from './itemCategories/itemsCategory.reducer';
import settings from './settings/settings.reducer'; import settings from './settings/settings.reducer';
import manualJournals from './manualJournals/manualJournals.reducers'; import manualJournals from './manualJournals/manualJournals.reducers';
import search from './search/search.reducer';
export default combineReducers({ export default combineReducers({
authentication, authentication,
@@ -30,4 +31,5 @@ export default combineReducers({
items, items,
itemCategories, itemCategories,
settings, settings,
search,
}); });

View File

@@ -0,0 +1,8 @@
import t from 'store/types';
export function generalSearch(name, result) {
return {
type: t.SEARCH_SUCCESS,
result,
};
}

View File

@@ -0,0 +1,33 @@
import t from 'store/types';
import { createReducer } from '@reduxjs/toolkit';
const initialState = {
searches: {},
searchTitle: 'Title',
isOpen: false,
};
export default createReducer(initialState, {
[t.SEARCH_SUCCESS]: (state, action) => {
const _result = {};
action.searches.forEach((search) => {
_result[search.id] = search;
});
state.searches = {
...state.searches,
..._result,
};
},
});
// return state = action.result;
// if (typeof state === 'undefined') {
// return initialState;
// }
// state.search[action.name] = {
// isOpen: true,
// payload: action.payload || {},
// };

View File

@@ -0,0 +1,5 @@
export default {
SEARCH_SUCCESS: 'SEARCH_SUCCESS',
SEARCH_FAILURE: 'SEARCH_FAILURE',
SEARCH_REQUEST: 'SEARCH_REQUEST',
};

View File

@@ -1,6 +1,6 @@
import authentication from './authentication/authentication.types'; import authentication from './authentication/authentication.types';
import accounts from './accounts/accounts.types'; import accounts from './accounts/accounts.types';
import accounting from './manualJournals/manualJournals.types' import accounting from './manualJournals/manualJournals.types';
import currencies from './currencies/currencies.types'; import currencies from './currencies/currencies.types';
import customFields from './customFields/customFields.types'; import customFields from './customFields/customFields.types';
import customViews from './customViews/customViews.types'; import customViews from './customViews/customViews.types';
@@ -13,7 +13,7 @@ import users from './users/users.types';
import financialStatements from './financialStatement/financialStatements.types'; import financialStatements from './financialStatement/financialStatements.types';
import itemCategories from './itemCategories/itemsCategory.type'; import itemCategories from './itemCategories/itemsCategory.type';
import settings from './settings/settings.type'; import settings from './settings/settings.type';
import search from './search/search.type';
export default { export default {
...authentication, ...authentication,
...accounts, ...accounts,
@@ -30,4 +30,5 @@ export default {
...itemCategories, ...itemCategories,
...settings, ...settings,
...accounting, ...accounting,
...search,
}; };

View File

@@ -15,30 +15,40 @@ export default {
router.use(JWTAuth); router.use(JWTAuth);
router.post('/:id', router.post(
'/:id',
// permit('create', 'edit'), // permit('create', 'edit'),
this.editCategory.validation, this.editCategory.validation,
asyncMiddleware(this.editCategory.handler)); asyncMiddleware(this.editCategory.handler)
);
router.post('/', router.post(
'/',
// permit('create'), // permit('create'),
this.newCategory.validation, this.newCategory.validation,
asyncMiddleware(this.newCategory.handler)); asyncMiddleware(this.newCategory.handler)
);
router.delete('/:id', router.delete(
'/:id',
// permit('create', 'edit', 'delete'), // permit('create', 'edit', 'delete'),
this.deleteItem.validation, this.deleteItem.validation,
asyncMiddleware(this.deleteItem.handler)); asyncMiddleware(this.deleteItem.handler)
);
router.get('/:id', router.get(
'/:id',
// permit('view'), // permit('view'),
this.getCategory.validation, this.getCategory.validation,
asyncMiddleware(this.getCategory.handler)); asyncMiddleware(this.getCategory.handler)
);
router.get('/', router.get(
'/',
// permit('view'), // permit('view'),
this.getList.validation, this.getList.validation,
asyncMiddleware(this.getList.handler)); asyncMiddleware(this.getList.handler)
);
return router; return router;
}, },
@@ -48,16 +58,26 @@ export default {
*/ */
newCategory: { newCategory: {
validation: [ validation: [
check('name').exists().trim().escape(), check('name')
check('parent_category_id').optional().isNumeric().toInt(), .exists()
check('description').optional().trim().escape(), .trim()
.escape(),
check('parent_category_id')
.optional({ nullable: true, checkFalsy: true })
.isNumeric()
.toInt(),
check('description')
.optional()
.trim()
.escape()
], ],
async handler(req, res) { async handler(req, res) {
const validationErrors = validationResult(req); const validationErrors = validationResult(req);
if (!validationErrors.isEmpty()) { if (!validationErrors.isEmpty()) {
return res.boom.badData(null, { return res.boom.badData(null, {
code: 'validation_error', ...validationErrors, code: 'validation_error',
...validationErrors
}); });
} }
@@ -66,20 +86,21 @@ export default {
if (form.parent_category_id) { if (form.parent_category_id) {
const foundParentCategory = await ItemCategory.query() const foundParentCategory = await ItemCategory.query()
.where('id', form.parent_category_id).first(); .where('id', form.parent_category_id)
.first();
if (!foundParentCategory) { if (!foundParentCategory) {
return res.boom.notFound('The parent category ID is not found.', { return res.boom.notFound('The parent category ID is not found.', {
errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }], errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }]
}); });
} }
} }
const category = await ItemCategory.query().insert({ const category = await ItemCategory.query().insert({
...form, ...form,
user_id: user.id, user_id: user.id
}); });
return res.status(200).send({ category }); return res.status(200).send({ category });
}, }
}, },
/** /**
@@ -88,9 +109,18 @@ export default {
editCategory: { editCategory: {
validation: [ validation: [
param('id').toInt(), param('id').toInt(),
check('name').exists().trim().escape(), check('name')
check('parent_category_id').optional().isNumeric().toInt(), .exists()
check('description').optional().trim().escape(), .trim()
.escape(),
check('parent_category_id')
.optional({ nullable: true, checkFalsy: true })
.isNumeric()
.toInt(),
check('description')
.optional()
.trim()
.escape()
], ],
async handler(req, res) { async handler(req, res) {
const { id } = req.params; const { id } = req.params;
@@ -98,33 +128,41 @@ export default {
if (!validationErrors.isEmpty()) { if (!validationErrors.isEmpty()) {
return res.boom.badData(null, { return res.boom.badData(null, {
code: 'validation_error', ...validationErrors, code: 'validation_error',
...validationErrors
}); });
} }
const form = { ...req.body }; const form = { ...req.body };
const itemCategory = await ItemCategory.query().where('id', id).first() const itemCategory = await ItemCategory.query()
.where('id', id)
.first();
if (!itemCategory) { if (!itemCategory) {
return res.boom.notFound({ return res.boom.notFound({
errors: [{ type: 'ITEM_CATEGORY.NOT.FOUND', code: 100 }], errors: [{ type: 'ITEM_CATEGORY.NOT.FOUND', code: 100 }]
}); });
} }
if (form.parent_category_id if (
&& form.parent_category_id !== itemCategory.parent_category_id) { form.parent_category_id &&
form.parent_category_id !== itemCategory.parent_category_id
) {
const foundParentCategory = await ItemCategory.query() const foundParentCategory = await ItemCategory.query()
.where('id', form.parent_category_id).first(); .where('id', form.parent_category_id)
.first();
if (!foundParentCategory) { if (!foundParentCategory) {
return res.boom.notFound('The parent category ID is not found.', { return res.boom.notFound('The parent category ID is not found.', {
errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }], errors: [{ type: 'PARENT_CATEGORY_NOT_FOUND', code: 100 }]
}); });
} }
} }
const updateItemCategory = await ItemCategory.query().where('id', id).update({ ...form }); const updateItemCategory = await ItemCategory.query()
.where('id', id)
.update({ ...form });
return res.status(200).send({ id: updateItemCategory }); return res.status(200).send({ id: updateItemCategory });
}, }
}, },
/** /**
@@ -132,20 +170,26 @@ export default {
*/ */
deleteItem: { deleteItem: {
validation: [ validation: [
param('id').exists().toInt(), param('id')
.exists()
.toInt()
], ],
async handler(req, res) { async handler(req, res) {
const { id } = req.params; const { id } = req.params;
const itemCategory = await ItemCategory.query().where('id', id).first(); const itemCategory = await ItemCategory.query()
.where('id', id)
.first();
if (!itemCategory) { if (!itemCategory) {
return res.boom.notFound(); return res.boom.notFound();
} }
await ItemCategory.query().where('id', itemCategory.id).delete(); await ItemCategory.query()
.where('id', itemCategory.id)
.delete();
return res.status(200).send(); return res.status(200).send();
}, }
}, },
/** /**
@@ -157,27 +201,25 @@ export default {
const categories = await ItemCategory.query(); const categories = await ItemCategory.query();
return res.status(200).send({ categories }); return res.status(200).send({ categories });
}, }
}, },
/** /**
* Retrieve details of the given category. * Retrieve details of the given category.
*/ */
getCategory: { getCategory: {
validation: [ validation: [param('category_id').toInt()],
param('category_id').toInt(),
],
async handler(req, res) { async handler(req, res) {
const { category_id: categoryId } = req.params; const { category_id: categoryId } = req.params;
const item = await ItemCategory.where('id', categoryId).fetch(); const item = await ItemCategory.where('id', categoryId).fetch();
if (!item) { if (!item) {
return res.boom.notFound(null, { return res.boom.notFound(null, {
errors: [{ type: 'CATEGORY_NOT_FOUND', code: 100 }], errors: [{ type: 'CATEGORY_NOT_FOUND', code: 100 }]
}); });
} }
return res.status(200).send({ category: item.toJSON() }); return res.status(200).send({ category: item.toJSON() });
}, }
}, }
}; };