216 lines
5.6 KiB
JavaScript
216 lines
5.6 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { debounce } from 'lodash';
|
|
import { Input, Col, Row, Select, Pagination } from 'antd';
|
|
import { Track } from './tracks/Track';
|
|
import { ManagedTrack } from './tracks/ManagedTrack';
|
|
import { useAppState } from '../hooks/useAppState';
|
|
import { useErrors } from '../hooks/useErrors';
|
|
import { fetchTacks, countTracks, fetchGenres } from '../api/calls';
|
|
import { useAbortableEffect } from '../hooks/useAbortableEffect';
|
|
import { requireEmployee } from '../util/constants';
|
|
import './TracksPage.css';
|
|
|
|
const { Search } = Input;
|
|
const { Option } = Select;
|
|
|
|
const DEBOUNCE_TIMER = 500;
|
|
const DEBOUNCE_OPTIONS = {
|
|
leading: true,
|
|
trailing: false,
|
|
};
|
|
|
|
const renderGenres = (genres) =>
|
|
genres.map(({ ID, name }) => (
|
|
<Option key={ID} value={ID.toString()}>
|
|
{name}
|
|
</Option>
|
|
));
|
|
|
|
const TracksContainer = () => {
|
|
const { setLoading, user } = useAppState();
|
|
const { handleError } = useErrors();
|
|
const [state, setState] = useState({
|
|
tracks: [],
|
|
genres: [],
|
|
pagination: {
|
|
currentPage: 1,
|
|
totalItems: 0,
|
|
pageSize: 20,
|
|
},
|
|
searchOptions: {
|
|
substr: '',
|
|
genreIds: [],
|
|
},
|
|
});
|
|
|
|
useAbortableEffect((status) => {
|
|
setLoading(true);
|
|
|
|
const countTracksReq = countTracks();
|
|
const getTracksRequest = fetchTacks();
|
|
const getGenresReq = fetchGenres();
|
|
|
|
Promise.all([countTracksReq, getTracksRequest, getGenresReq])
|
|
.then(
|
|
([
|
|
{ data: totalItems },
|
|
{
|
|
data: { value: tracks },
|
|
},
|
|
{
|
|
data: { value: genres },
|
|
},
|
|
]) => {
|
|
if (!status.aborted) {
|
|
setState({
|
|
...state,
|
|
tracks,
|
|
genres,
|
|
pagination: { ...state.pagination, totalItems },
|
|
});
|
|
}
|
|
}
|
|
)
|
|
.catch(handleError)
|
|
.finally(() => setLoading(false));
|
|
}, []);
|
|
|
|
const onSearch = debounce(
|
|
() => {
|
|
setLoading(true);
|
|
const options = {
|
|
$top: state.pagination.pageSize,
|
|
substr: state.searchOptions.substr.replace(`'`, `''`),
|
|
genreIds: state.searchOptions.genreIds,
|
|
};
|
|
|
|
Promise.all([
|
|
fetchTacks(options),
|
|
countTracks({
|
|
substr: options.substr,
|
|
genreIds: options.genreIds,
|
|
}),
|
|
])
|
|
.then(([{ data: { value: tracks } }, { data: totalItems }]) =>
|
|
setState({
|
|
...state,
|
|
tracks,
|
|
pagination: { ...state.pagination, totalItems },
|
|
})
|
|
)
|
|
.catch(handleError)
|
|
.finally(() => setLoading(false));
|
|
},
|
|
DEBOUNCE_TIMER,
|
|
DEBOUNCE_OPTIONS
|
|
);
|
|
const onSelectChange = (genres) => {
|
|
setState({
|
|
...state,
|
|
searchOptions: {
|
|
...state.searchOptions,
|
|
genreIds: genres.map((value) => parseInt(value, 10)),
|
|
},
|
|
});
|
|
};
|
|
const onSearchChange = (event) => {
|
|
setState({
|
|
...state,
|
|
searchOptions: { ...state.searchOptions, substr: event.target.value },
|
|
});
|
|
};
|
|
const onChangePage = (pageNumber) => {
|
|
document.querySelector('section.ant-layout').scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
setLoading(true);
|
|
|
|
const options = {
|
|
$top: state.pagination.pageSize,
|
|
substr: state.searchOptions.substr,
|
|
genreIds: state.searchOptions.genreIds,
|
|
$skip: (pageNumber - 1) * state.pagination.pageSize,
|
|
};
|
|
fetchTacks(options)
|
|
.then((response) =>
|
|
setState({
|
|
...state,
|
|
tracks: response.data.value,
|
|
pagination: { ...state.pagination, currentPage: pageNumber },
|
|
})
|
|
)
|
|
.catch(handleError)
|
|
.finally(() => setLoading(false));
|
|
};
|
|
const deleteTrack = (ID) => {
|
|
setState({
|
|
...state,
|
|
tracks: state.tracks.filter(({ ID: curID }) => curID !== ID),
|
|
});
|
|
};
|
|
const renderTracks = (tracks) => {
|
|
const isEmployee = requireEmployee(user);
|
|
const TrackComponent = isEmployee ? ManagedTrack : Track;
|
|
return tracks.map((track) => {
|
|
const isAlreadyOrdered = !isEmployee && track.alreadyOrdered;
|
|
const onDeleteTrack = isEmployee && ((ID) => deleteTrack(ID));
|
|
return (
|
|
<Col key={`track-col${track.ID}`} className="gutter-row" span={8}>
|
|
<TrackComponent
|
|
initialTrack={track}
|
|
onDeleteTrack={onDeleteTrack}
|
|
isAlreadyOrdered={isAlreadyOrdered}
|
|
/>
|
|
</Col>
|
|
);
|
|
});
|
|
};
|
|
|
|
const trackElements = renderTracks(state.tracks);
|
|
const genreElements = renderGenres(state.genres);
|
|
|
|
return (
|
|
<>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'start',
|
|
maxWidth: 600,
|
|
paddingBottom: 10,
|
|
}}
|
|
>
|
|
<Select
|
|
mode="multiple"
|
|
allowClear
|
|
style={{ marginRight: 10, borderRadius: 6 }}
|
|
placeholder="Genres"
|
|
onChange={(value) => onSelectChange(value)}
|
|
>
|
|
{genreElements}
|
|
</Select>
|
|
<Search
|
|
style={{
|
|
borderRadius: 6,
|
|
}}
|
|
placeholder="Search tracks"
|
|
size="large"
|
|
onSearch={onSearch}
|
|
onChange={onSearchChange}
|
|
/>
|
|
</div>
|
|
<div>
|
|
<Row gutter={[{ xs: 8, sm: 16, md: 24, lg: 32 }, 24]}>{trackElements}</Row>
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
<Pagination
|
|
showSizeChanger={false}
|
|
defaultCurrent={1}
|
|
total={state.pagination.totalItems}
|
|
pageSize={state.pagination.pageSize}
|
|
onChange={onChangePage}
|
|
/>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default TracksContainer;
|