Browse Source

Поиск объектов

master
parent
commit
1705d1a077
  1. 4
      deploy.sh
  2. 13
      package-lock.json
  3. 4
      package.json
  4. 1
      src/App.js
  5. 145
      src/components/Objects.js
  6. 32
      src/components/SelectCategories.js
  7. 22
      src/index.css
  8. 26
      src/stores/LocalStore.js
  9. 27
      src/stores/ObjectsStore.js

4
deploy.sh

@ -1,4 +1,4 @@
#!/bin/bash
ssh bastion 'mv /usr/share/nginx/html/govno /usr/share/nginx/html/govno_bak_$(date +"%y_%m_%d__%H_%M_%S")'
ssh bastion 'mv /usr/share/nginx/html/dew /usr/share/nginx/html/dew_bak_$(date +"%y_%m_%d__%H_%M_%S")'
scp -r build bastion:/usr/share/nginx/html
ssh bastion 'mv /usr/share/nginx/html/build /usr/share/nginx/html/govno'
ssh bastion 'mv /usr/share/nginx/html/build /usr/share/nginx/html/dew'

13
package-lock.json generated

@ -10971,6 +10971,14 @@
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-paginate": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-6.5.0.tgz",
"integrity": "sha512-H7xSi9jyiJzgfaj+2nNhQcjZfwzJ/Mxb64V2RiyDctjZyCWojwsaGwMqhLBpQ58iAuMVtBMRQ7ECqMcUKG9QSQ==",
"requires": {
"prop-types": "^15.6.1"
}
},
"react-popper": {
"version": "1.3.7",
"resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
@ -11087,6 +11095,11 @@
"react-lifecycles-compat": "^3.0.4"
}
},
"react-window-infinite-loader": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/react-window-infinite-loader/-/react-window-infinite-loader-1.0.5.tgz",
"integrity": "sha512-IcPIq8lADK3zsAcqoLqQGyduicqR6jWkiK2VUX5sKSI9X/rou6OWlOEexnGyujdNTG7hSG8OVBFEhLSDs4qrxg=="
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",

4
package.json

@ -20,8 +20,10 @@
"react-dom": "^16.14.0",
"react-helmet-async": "^1.0.7",
"react-hotkeys": "^2.0.0",
"react-paginate": "^6.5.0",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.4"
"react-scripts": "^3.4.4",
"react-window-infinite-loader": "^1.0.5"
},
"scripts": {
"start": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` react-scripts start",

1
src/App.js

@ -61,7 +61,6 @@ const App = observer(() => {
const containerStyle = {
display: 'flex',
height: '100vh',
flexDirection: 'column',
};

145
src/components/Objects.js

@ -1,4 +1,4 @@
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import {
Spinner,
HTMLTable,
@ -9,12 +9,16 @@ import {
NonIdealState,
Callout,
Intent,
Classes,
Dialog,
NumericInput,
} from '@blueprintjs/core';
import { observer } from 'mobx-react';
import { Link, useHistory, useParams } from 'react-router-dom';
import { useStores } from '../hooks/useStores';
import { dateTime } from '../utils/dateTime';
import SelectCategories from './SelectCategories';
import ReactPaginate from 'react-paginate';
const Objects = observer(props => {
let { catID } = useParams();
@ -29,28 +33,39 @@ const Objects = observer(props => {
categories,
searchMask,
setSearchMask,
selectedCategories,
setSelectedCategories,
selectedCategories,
count,
} = rootStore.objectsStore;
const { objectsPerPage, setObjectsPerPage } = rootStore.localStore;
let history = useHistory();
let [showSettings, setShowSettings] = useState(false);
useEffect(() => {
setSelectedCategories(catID);
}, [catID, setSelectedCategories, categories]);
useEffect(() => {
if (searchMask.length > 2) {
console.log('rerender');
selectedCategories.map(c => console.log(c.ID_CAT));
console.log(selectedCategories);
console.log(JSON.stringify(selectedCategories));
getObjects(
searchMask,
categories.map(c => c.ID_CAT)
);
if (searchMask === '') {
getObjects(searchMask, true);
return;
}
}, [searchMask]);
const delayDebounceFn = setTimeout(() => {
getObjects(searchMask, true);
}, 300);
return () => clearTimeout(delayDebounceFn);
}, [searchMask, getObjects, selectedCategories.length]);
const handlePageClick = data => {
let selected = data.selected;
let offset = Math.ceil(selected * 3);
getObjects(searchMask, false, offset);
};
const typeImage = id => {
if (types.length) {
@ -59,6 +74,33 @@ const Objects = observer(props => {
}
};
const settingsDialog = (
<Dialog
icon="cog"
isOpen={showSettings}
onClose={() => {
setShowSettings(false);
}}
title="Поиск объектов">
<div className={Classes.DIALOG_BODY}>
<FormGroup label="Объектов на странице" labelFor="text-input">
<NumericInput
placeholder="Введите число"
value={objectsPerPage}
onValueChange={number => setObjectsPerPage(number)}
/>
</FormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>
<div className={Classes.DIALOG_FOOTER_ACTIONS}>
<Button onClick={() => setShowSettings(false)} intent={Intent.PRIMARY}>
Закрыть
</Button>
</div>
</div>
</Dialog>
);
const objsList = (
<>
<HTMLTable interactive={true}>
@ -98,12 +140,30 @@ const Objects = observer(props => {
</>
);
let clearButton = <Button icon="cross" disabled={!searchMask} minimal={true} onClick={() => (searchMask = '')} />;
let clearButton = <Button icon="cross" disabled={!searchMask} minimal={true} onClick={() => setSearchMask('')} />;
let noResults = <NonIdealState icon={'search'} title={`Нет результатов по запросу «${searchMask}»`} />;
let noResults = (
<NonIdealState
icon={'search'}
title={searchMask.length > 2 ? `Нет результатов по запросу "${searchMask}"` : 'Введите запрос'}
let paginator = (
<ReactPaginate
previousLabel={'<'}
nextLabel={'>'}
breakLabel={'...'}
pageCount={Math.floor(count / objectsPerPage)}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
onPageChange={handlePageClick}
containerClassName={'bp3-button-group'}
subContainerClassName={'bp3-button'}
activeClassName={'bp3-active bp3-intent-primary'}
activeLinkClassName={'objects-search-page-active'}
pageLinkClassName={'objects-search-page'}
breakLinkClassName={'objects-search-dots'}
disabledClassName={'bp3-disabled'}
pageClassName={'bp3-button'}
previousClassName={'bp3-button'}
nextClassName={'bp3-button'}
breakClassName={'bp3-button'}
/>
);
@ -142,14 +202,18 @@ const Objects = observer(props => {
return text.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
const noCat = !!catID && !!categories.length && !categories.some(c => c.ID_CAT === Number(catID));
return (
<>
{settingsDialog}
<ul class="bp3-breadcrumbs">
<li>
<span className="bp3-breadcrumb bp3-breadcrumb-current">Объекты</span>
</li>
</ul>{' '}
<FormGroup label={'Фильтр'} labelInfo="(минимум 3 символа)">
</ul>
<FormGroup label={'Фильтр'}>
<ControlGroup>
<InputGroup
style={{ width: 500 }}
@ -158,6 +222,9 @@ const Objects = observer(props => {
rightElement={clearButton}
onChange={e => setSearchMask(e.target.value)}
/>
<Button icon="cog" onClick={() => setShowSettings(true)}>
Настройки поиска
</Button>
{fetching && (
<div style={{ marginLeft: 10, display: 'flex', alignItems: 'center' }}>
<Spinner size={Spinner.SIZE_SMALL} />
@ -165,14 +232,42 @@ const Objects = observer(props => {
)}
</ControlGroup>
</FormGroup>
<SelectCategories />
{!!catID && !!categories.length && !categories.some(c => c.ID_CAT === Number(catID)) && (
<SelectCategories
callback={() => {
getObjects(searchMask, true);
}}
/>
{noCat && (
<FormGroup>
<Callout intent={Intent.DANGER}>Категория {catID} не найдена!</Callout>{' '}
<Callout intent={Intent.DANGER}>Категория {catID} не найдена!</Callout>
</FormGroup>
)}
{objects.length === 0 || (searchMask === '' && !fetching) ? noResults : objsList}
<div style={{ height: '100%' }}>
{!fetching && (
<div style={{ height: '100%' }}>
{objects.length !== 0 && objsList}
{objects.length === 0 && noResults}
</div>
)}
{objects.length === 0 && fetching && (
<div style={{ height: '100%', display: 'flex', justifyContent: 'center' }}>
<Spinner />
</div>
)}
</div>
{(objects.length || fetching) && (
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div style={{ alignSelf: 'center' }}>
<p>
{selectedCategories.length === categories.length ? 'В базе: ' : 'По фильтру: '} {count}
</p>
</div>
<div style={{ flexGrow: 1, textAlign: 'center' }}>{count > objectsPerPage && paginator}</div>
<div style={{ alignSelf: 'center' }}>
<Button>Загрузить все</Button>
</div>
</div>
)}
</>
);
});

32
src/components/SelectCategories.js

@ -8,6 +8,8 @@ const SelectCategories = observer(props => {
const { rootStore } = useStores();
let { categories, selectedCategories } = rootStore.objectsStore;
const values = selectedCategories.map(c => c.NAME_SHORT);
const handleCatSelect = item => {
if (selectedCategories.includes(item)) {
selectedCategories.remove(item);
@ -36,6 +38,20 @@ const SelectCategories = observer(props => {
});
};
const handleTagAdd = tags => {
for (let tag of tags) {
let cat = categories.find(c => c.NAME_SHORT === tag || c.NAME === tag || c.ID_CAT === Number(tag));
if (cat && !selectedCategories.includes(cat)) {
selectedCategories.push(cat);
}
}
};
const handleTagRemove = tag => {
let cat = selectedCategories.find(c => c.NAME_SHORT === tag);
selectedCategories.remove(cat);
};
const menu = (
<Menu>
<MenuDivider title="Выбрать..." />
@ -74,22 +90,6 @@ const SelectCategories = observer(props => {
</Menu>
);
const values = selectedCategories.map(c => c.NAME_SHORT);
const handleTagAdd = tags => {
for (let tag of tags) {
let cat = categories.find(c => c.NAME_SHORT === tag || c.NAME === tag || c.ID_CAT === Number(tag));
if (cat && !selectedCategories.includes(cat)) {
selectedCategories.push(cat);
}
}
};
const handleTagRemove = tag => {
let cat = selectedCategories.find(c => c.NAME_SHORT === tag);
selectedCategories.remove(cat);
};
return (
<>
<FormGroup

22
src/index.css

@ -1,3 +1,19 @@
@import "~normalize.css";
@import "~@blueprintjs/core/lib/css/blueprint.css";
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
@import '~normalize.css';
@import '~@blueprintjs/core/lib/css/blueprint.css';
@import '~@blueprintjs/icons/lib/css/blueprint-icons.css';
.objects-search-dots {
color: #999;
}
.objects-search-page:hover {
text-decoration: none;
color: #000;
}
.objects-search-page {
color: #000;
}
.objects-search-page-active {
color: #fff;
}

26
src/stores/LocalStore.js

@ -1,9 +1,10 @@
import { observable, action, decorate, computed, when } from 'mobx';
import { observable, action, decorate, when } from 'mobx';
import moment from 'moment';
class LocalStore {
darkTheme = false;
changeThemeOnTime = true;
objectsPerPage = 10;
time = moment();
@ -35,6 +36,18 @@ class LocalStore {
}
)
}
let number = Number(localStorage.getItem('objectsPerPage'));
if (Number.isInteger(number) && number > 0) {
this.objectsPerPage = number;
} else {
localStorage.removeItem('objectsPerPage');
}
if (localStorage.getItem('changeThemeOnTime') === 'no') {
this.changeThemeOnTime = false;
}
}
clockTick(newTime = moment()) {
@ -60,6 +73,13 @@ class LocalStore {
this.changeThemeOnTime = false;
}
}
setObjectsPerPage = (number) => {
if (Number.isInteger(number) && number > 0) {
localStorage.setItem('objectsPerPage', number);
this.objectsPerPage = number;
}
}
}
decorate(LocalStore, {
@ -68,7 +88,9 @@ decorate(LocalStore, {
clockTick: action,
time: observable,
setChangeThemeOnTime: action,
changeThemeOnTime: observable
changeThemeOnTime: observable,
setObjectsPerPage: action,
objectsPerPage: observable
});
export default LocalStore;

27
src/stores/ObjectsStore.js

@ -82,19 +82,37 @@ class ObjectsStore {
});
};
getObjects = (searchMask, catIDs) => {
count = 0;
getObjects = (searchMask, newSearch = false, offset = 0) => {
let batch = this.rootStore.localStore.objectsPerPage;
this.objects.clear();
if (newSearch) {
this.count = 0;
}
if (this.selectedCategories.length === 0) {
this.count = 0;
return;
}
this.fetching = true;
const req = { SearchMask: `%${searchMask}%`, CategoryIDs: catIDs };
const req = { SearchMask: `%${searchMask}%`, CategoryIDs: this.selectedCategories.map(c => c.ID_CAT) };
this.rootStore.loginStore.apiAuthRequest(req, 'api-objects-search', res => {
this.count = res.FoundObjectKeys.length;
if (res.FoundObjectKeys.length === 0) {
this.fetching = false;
} else {
res.FoundObjectKeys.map(k => {
let arr = res.FoundObjectKeys.slice(offset, offset + batch);
arr.map(k => {
const req = { GetAll: false, ObjectIDs: [k.ID], CategoryID: k.ID_CAT };
this.rootStore.loginStore.apiAuthRequest(req, 'api-objects-get', resObj => {
this.objects.push(resObj.Objects[0]);
if (k === res.FoundObjectKeys[res.FoundObjectKeys.length - 1]) {
if (k === arr[arr.length - 1]) {
this.fetching = false;
}
});
@ -146,6 +164,7 @@ decorate(ObjectsStore, {
searchMask: observable,
setSearchMask: action,
selectedCategories: observable,
count: observable,
});
export default ObjectsStore;

Loading…
Cancel
Save