Last active
August 25, 2020 13:22
-
-
Save az67128/351a4baa248f7e3a231d29038284afe2 to your computer and use it in GitHub Desktop.
API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {getStore} from '../tools/connect'; | |
import {flattenError} from '../tools/index'; | |
import {environment} from '../configs'; | |
import keysToCamel from './keysToCamel'; | |
import mock from './mock'; | |
import getApiPath from './getApiPath'; | |
export {getApiPath}; | |
const callApi = (path, options) => { | |
const url = getApiPath(path); | |
const responseHeaders = {}; | |
if (environment === 'test') return mock(url, options); | |
return fetch(url, { | |
credentials: 'include', | |
headers: options.headers ? options.headers : {'Content-type': 'application/json; charset=utf-8'}, | |
...options, | |
}) | |
.then((res) => { | |
if (res.status === 401) { | |
// timeout to safely complete current store request | |
const store = getStore(); | |
if (store) setTimeout(() => store.auth.logout(false), 0); | |
throw new Error('Сессия истекла. Авторизуйтесь заново'); | |
} | |
if (res.status === 500 || res.status === 504 || res.status === 502) { | |
throw new Error('На сервере произошла ошибка, повторите попытку позже'); | |
} | |
return res; | |
}) | |
.then((res) => { | |
if (options.withHeaders) { | |
for (const [name, value] of res.headers.entries()) { | |
responseHeaders[name] = value; | |
} | |
} | |
switch (options.format) { | |
case 'json': | |
return res.json(); | |
case 'text': | |
return res.text(); | |
case 'blob': | |
return res.blob(); | |
case 'formData': | |
return res.formData(); | |
case 'arrayBuffer': | |
return res.arrayBuffer(); | |
default: | |
return res.json(); | |
} | |
}) | |
.then((data) => { | |
if (data.errorText && data.errorFields) { | |
const errorText = flattenError(data.errorFields); | |
const error = new Error(errorText); | |
error.body = {...data, errorText}; | |
throw error; | |
} else if (data.errorText) { | |
const error = new Error(typeof data.errorText === 'string' ? data.errorText : 'Неизвестная Ошибка'); | |
error.body = data; | |
throw error; | |
} else { | |
const responseData = options.toCamel === false ? data : keysToCamel(data); | |
return options.withHeaders ? {data: responseData, headers: responseHeaders} : responseData; | |
} | |
}); | |
}; | |
const api = { | |
get: (path, options = {}) => callApi(path, {...options, method: 'GET'}), | |
post: (path, options = {}) => callApi(path, {...options, method: 'POST', body: JSON.stringify(options.body || '')}), | |
patch: (path, options = {}) => callApi(path, {...options, method: 'PATCH', body: JSON.stringify(options.body || '')}), | |
put: (path, options = {}) => callApi(path, {...options, method: 'PUT', body: JSON.stringify(options.body || '')}), | |
delete: (path, options = {}) => callApi(path, {...options, method: 'DELETE', body: JSON.stringify(options.body || '')}), | |
upload: (path, options = {}) => callApi(path, {...options, method: 'POST', body: options.body, headers: {...(options.headers || {})}}), | |
}; | |
export default api; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {types, flow, getRoot} from 'mobx-state-tree'; | |
import api from 'api'; | |
import List from 'stores/commonModels/list'; | |
import Client from './client.js'; | |
const Clients = types | |
.model('Clients', { | |
list: types.optional(List(Client), { | |
searchField: 'name_or_inn_or_phone', | |
isLoading: true, | |
}), | |
}) | |
.actions((self) => { | |
const model = self; | |
const {addNotification} = getRoot(self).common; | |
const getList = flow(function* ajax() { | |
model.list.isLoading = true; | |
try { | |
const {data} = yield api | |
.get(`api/partner_access/?&sort_type=desc&sort_key=status&${model.list.queryString}`); | |
model.list.items = data.requests; | |
model.list.count = data.count; | |
} catch (err) { | |
addNotification(err); | |
} | |
model.list.isLoading = false; | |
}); | |
return {getList}; | |
}); | |
export default Clients; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {types, getRoot, getParent, addMiddleware, createActionTrackingMiddleware2} from 'mobx-state-tree'; | |
import {reaction} from 'mobx'; | |
import history from 'tools/history'; | |
import Filter from './filter'; | |
import DateFilter from './dateFilter'; | |
import SortItem from './sortItem'; | |
/** | |
* | |
* List depends on getList action of parent. getList will be triggered on search, filters and pagination. | |
* | |
* On component mount call attach action. | |
* On component unmount call detach action | |
* | |
*/ | |
const List = (ItemsModel) => { | |
const Model = types | |
.model('List', { | |
items: types.array(ItemsModel), | |
count: types.optional(types.number, 0), | |
offset: types.optional(types.number, 0), | |
limit: types.optional(types.number, 10), | |
isLoading: types.optional(types.boolean, false), | |
filters: types.array( | |
types.union({ | |
dispatcher(snapshot) { | |
return snapshot.type === 'date' ? DateFilter : Filter; | |
}, | |
Filter, | |
DateFilter, | |
}), | |
), | |
sorts: types.array(SortItem), | |
searchField: types.maybeNull(types.string), | |
queryTimeout: types.optional(types.number, 300), | |
}) | |
.volatile(() => ({ | |
queryTimeoutId: null, | |
getListActionId: null, | |
reactionDisposers: [], | |
historyDisposer: () => {}, | |
})) | |
.actions((self) => { | |
const model = self; | |
const {quickSearch} = getRoot(model); | |
const {getList} = getParent(model); | |
const abortGetListMiddleware = (call, next, abort) => { | |
if ( | |
call.parentActionEvent | |
&& call.parentActionEvent.id !== model.getListActionId | |
&& call.parentActionEvent.name === 'getList' | |
) { | |
abort(call); | |
} else { | |
next(call); | |
} | |
}; | |
const getListTrackingMiddleware = createActionTrackingMiddleware2({ | |
filter: (call) => call.name === 'getList', | |
onStart: (call) => { | |
model.getListActionId = call.id; | |
}, | |
onFinish: () => {}, | |
}); | |
addMiddleware(model.items, abortGetListMiddleware); | |
addMiddleware(getParent(model), getListTrackingMiddleware); | |
const attach = () => { | |
model.getStateFromSearch(); | |
model.historyDisposer = history.listen((location, action) => { | |
if (action === 'PUSH' || action === 'POP') { | |
setTimeout(() => { | |
model.getStateFromSearch(); | |
// execute if history not disposed | |
if (model.historyDisposer) model.scheduleRefresh(0); | |
}, 0); | |
} | |
}); | |
model.reactionDisposers = [ | |
reaction( | |
() => [ | |
model.filterQueryString, | |
model.searchQueryString, | |
model.pagingQueryString, | |
model.sortQueryString, | |
], | |
() => { | |
const historySearch = new URLSearchParams(history.location.search); | |
const listSearch = new URLSearchParams(model.queryString); | |
[ | |
'offset', | |
'limit', | |
'sort_by', | |
...(model.searchField ? [model.searchField] : []), | |
...model.filters | |
.map((filter) => { | |
if (filter.type === 'date') return [`${filter.prefix}from_dt`, `${filter.prefix}to_dt`]; | |
return filter.name; | |
}) | |
.flat(), | |
].forEach((key) => historySearch.delete(key)); | |
for (const [name, value] of listSearch) { | |
historySearch.append(name, value); | |
} | |
history.replace({ | |
search: '?' + historySearch.toString(), | |
}); | |
}, | |
), | |
reaction( | |
() => [model.filterQueryString, model.searchQueryString, model.sortQueryString], | |
() => { | |
model.scheduleRefresh(); | |
model.setOffset(0); | |
}, | |
), | |
reaction(() => model.pagingQueryString, model.scheduleRefresh), | |
]; | |
model.scheduleRefresh(0); | |
}; | |
const scheduleRefresh = (timeout = model.queryTimeout) => { | |
if (!getList) return; | |
clearTimeout(model.queryTimeoutId); | |
model.queryTimeoutId = setTimeout(getList, timeout); | |
}; | |
const getStateFromSearch = () => { | |
model.resetFilters(); | |
model.sorts = []; | |
const historySearch = new URLSearchParams(history.location.search); | |
if (historySearch.has('offset')) model.offset = Number(historySearch.get('offset'), 10); | |
if (historySearch.has('limit')) model.limit = Number(historySearch.get('limit'), 10); | |
if (historySearch.has(model.searchField)) { | |
quickSearch.changeSearchString(historySearch.get(model.searchField)); | |
} | |
model.sorts = historySearch | |
.getAll('sort_by') | |
.map((item) => ({name: item.slice(1), direction: item.slice(0, 1)})); | |
model.filters.forEach((filter) => { | |
if (filter.type === 'date') { | |
filter.set([ | |
historySearch.get(filter.prefix + 'from_dt'), | |
historySearch.get(filter.prefix + 'to_dt'), | |
]); | |
} else { | |
if (!historySearch.has(filter.name)) return; | |
historySearch.getAll(filter.name).forEach((item) => { | |
let val = item; | |
if (filter.type === 'boolean') { | |
val = val === 'true'; | |
} | |
filter.toggleByValue(val); | |
}); | |
} | |
}); | |
}; | |
const resetFilters = () => { | |
model.filters.forEach((filter) => filter.reset()); | |
}; | |
const setOffset = (offset) => { | |
model.offset = offset; | |
}; | |
const setPageSize = (pageSize) => { | |
model.limit = pageSize; | |
}; | |
const detach = () => { | |
model.historyDisposer(); | |
model.historyDisposer = null; | |
model.reactionDisposers.forEach((dispose) => dispose()); | |
model.offset = 0; | |
model.resetFilters(); | |
quickSearch.reset(); | |
}; | |
const toggleSort = (name) => { | |
const target = model.sort[name]; | |
if (target && target.direction === '+') { | |
model.sorts = model.sorts.filter((item) => item.name !== name); | |
} else if (target && target.direction === '-') { | |
target.direction = '+'; | |
} else { | |
model.sorts.push({name, value: '-'}); | |
} | |
}; | |
return { | |
attach, | |
setOffset, | |
setPageSize, | |
getStateFromSearch, | |
scheduleRefresh, | |
detach, | |
resetFilters, | |
toggleSort, | |
}; | |
}) | |
.views((model) => ({ | |
get filter() { | |
const filters = {}; | |
model.filters.forEach((item) => { | |
filters[item.id] = item; | |
}); | |
return filters; | |
}, | |
get sort() { | |
const sorts = {}; | |
model.sorts.forEach((item) => { | |
sorts[item.name] = item; | |
}); | |
return sorts; | |
}, | |
get filterQueryString() { | |
return model.filters | |
.map((item) => item.queryString) | |
.filter(Boolean) | |
.join('&'); | |
}, | |
get pagingQueryString() { | |
return `offset=${model.offset}&limit=${model.limit}`; | |
}, | |
get searchQueryString() { | |
if (!model.searchField) return ''; | |
const {searchString} = getRoot(model).quickSearch; | |
return searchString ? `${model.searchField}=${encodeURIComponent(searchString)}` : ''; | |
}, | |
get sortQueryString() { | |
return model.sorts.map((item) => `sort_by=${encodeURIComponent(item.direction)}${item.name}`).join('&'); | |
}, | |
get queryString() { | |
return [ | |
model.pagingQueryString, | |
model.filterQueryString, | |
model.searchQueryString, | |
model.sortQueryString, | |
] | |
.filter(Boolean) | |
.join('&'); | |
}, | |
})); | |
return Model; | |
}; | |
export default List; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment