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

@@ -1,6 +1,6 @@
import React from 'react';
import {connect} from 'react-redux';
import {useHistory} from 'react-router';
import { connect } from 'react-redux';
import { useHistory } from 'react-router';
import {
Navbar,
NavbarGroup,
@@ -14,59 +14,79 @@ import {
import DashboardBreadcrumbs from 'components/Dashboard/DashboardBreadcrumbs';
import DashboardTopbarUser from 'components/Dashboard/TopbarUser';
import Icon from 'components/Icon';
import Search from 'containers/Dashboard/GeneralSearch/Search';
function DashboardTopbar({
pageTitle,
pageSubtitle,
editViewId,
}) {
function DashboardTopbar({ pageTitle, pageSubtitle, editViewId }) {
const history = useHistory();
const handlerClickEditView = () => {
history.push(`/dashboard/custom_views/${editViewId}/edit`)
}
history.push(`/dashboard/custom_views/${editViewId}/edit`);
};
const maybleRenderPageSubtitle = pageSubtitle && (<h3>{ pageSubtitle }</h3>);
const maybeRenderEditViewBtn = (pageSubtitle && editViewId) && (
const maybleRenderPageSubtitle = pageSubtitle && <h3>{pageSubtitle}</h3>;
const maybeRenderEditViewBtn = pageSubtitle && editViewId && (
<Button
className={Classes.MINIMAL + ' button--view-edit'}
icon={<Icon icon="pen" iconSize={13} />}
onClick={handlerClickEditView} />
icon={<Icon icon='pen' iconSize={13} />}
onClick={handlerClickEditView}
/>
);
return (
<div class="dashboard__topbar">
<div class="dashboard__topbar-left">
<div class="dashboard__topbar-sidebar-toggle">
<div class='dashboard__topbar'>
<div class='dashboard__topbar-left'>
<div class='dashboard__topbar-sidebar-toggle'>
<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>
<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>
</Button>
</div>
<div class="dashboard__title">
<h1>{ pageTitle }</h1>
<div class='dashboard__title'>
<h1>{pageTitle}</h1>
{maybleRenderPageSubtitle}
{maybeRenderEditViewBtn}
</div>
<div class="dashboard__breadcrumbs">
<div class='dashboard__breadcrumbs'>
<DashboardBreadcrumbs />
</div>
</div>
<div class="dashboard__topbar-right">
<Navbar class="dashboard__topbar-navbar">
<div class='dashboard__topbar-right'>
<Navbar class='dashboard__topbar-navbar'>
<NavbarGroup>
<Button className={Classes.MINIMAL} icon="home" text="Search" />
<Button className={Classes.MINIMAL} icon="document" text="Filters" />
<Button className={Classes.MINIMAL} icon="document" text="Add order" />
<Button className={Classes.MINIMAL} icon="document" text="More" />
{/* <Button className={Classes.MINIMAL} icon="home" text="Search" /> */}
<Search className />
<Button
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>
</Navbar>
<div class="dashboard__topbar-user">
<div class='dashboard__topbar-user'>
<DashboardTopbarUser />
</div>
</div>
@@ -77,7 +97,7 @@ function DashboardTopbar({
const mapStateToProps = (state) => ({
pageTitle: state.dashboard.pageTitle,
pageSubtitle: state.dashboard.pageSubtitle,
editViewId: state.dashboard.topbarEditViewId,
editViewId: state.dashboard.topbarEditViewId,
});
export default connect(mapStateToProps)(DashboardTopbar);
export default connect(mapStateToProps)(DashboardTopbar);

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({
views,
manualJournals,
setTopbarEditView,
customViewChanged,
addManualJournalsTableQueries,
@@ -51,8 +50,6 @@ function ManualJournalsViewTabs({
}, [customViewId]);
const tabs = views.map((view) => {
//FIXME: dashboard/accounting/make-journal-entry
const baseUrl = '/dashboard/accounting/manual-journals';
const 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';
export const submitItemCategory = ({ form }) => {
return dispatch => {
return (dispatch) => {
return ApiService.post('item_categories', { ...form });
};
};
@@ -11,53 +11,53 @@ export const fetchItemCategories = () => {
return (dispatch, getState) =>
new Promise((resolve, reject) => {
ApiService.get('item_categories')
.then(response => {
.then((response) => {
dispatch({
type: t.ITEMS_CATEGORY_LIST_SET,
categories: response.data.categories
categories: response.data.categories,
});
resolve(response);
})
.catch(error => {
.catch((error) => {
reject(error);
});
});
};
export const editItemCategory = (id, form) => {
return dispatch =>
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.post(`item_categories/${id}`, form)
.then(response => {
.then((response) => {
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS });
resolve(response);
})
.catch(error => {
.catch((error) => {
const { response } = error;
const { data } = response;
const { errors } = data;
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS });
if (errors) {
dispatch({ type: t.CATEGORY_FORM_ERRORS, errors });
dispatch({ type: t.CLEAR_CATEGORY_FORM_ERRORS, errors });
}
reject(error);
});
});
};
export const deleteItemCategory = id => {
return dispatch =>
export const deleteItemCategory = (id) => {
return (dispatch) =>
new Promise((resolve, reject) => {
ApiService.delete(`item_categories/${id}`)
.then(response => {
.then((response) => {
dispatch({
type: t.CATEGORY_DELETE,
id
id,
});
resolve(response);
})
.catch(error => {
.catch((error) => {
reject(error);
});
});

View File

@@ -14,6 +14,7 @@ import financialStatements from './financialStatement/financialStatements.reduce
import itemCategories from './itemCategories/itemsCategory.reducer';
import settings from './settings/settings.reducer';
import manualJournals from './manualJournals/manualJournals.reducers';
import search from './search/search.reducer';
export default combineReducers({
authentication,
@@ -30,4 +31,5 @@ export default combineReducers({
items,
itemCategories,
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 accounts from './accounts/accounts.types';
import accounting from './manualJournals/manualJournals.types'
import accounting from './manualJournals/manualJournals.types';
import currencies from './currencies/currencies.types';
import customFields from './customFields/customFields.types';
import customViews from './customViews/customViews.types';
@@ -13,7 +13,7 @@ import users from './users/users.types';
import financialStatements from './financialStatement/financialStatements.types';
import itemCategories from './itemCategories/itemsCategory.type';
import settings from './settings/settings.type';
import search from './search/search.type';
export default {
...authentication,
...accounts,
@@ -30,4 +30,5 @@ export default {
...itemCategories,
...settings,
...accounting,
...search,
};

View File

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