Compare commits
10 Commits
39ec47c6d7
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06e8ca0cf7 | ||
|
|
191ae2689b | ||
|
|
d21242d24c | ||
|
|
2952be5ca3 | ||
|
|
3eed7336a5 | ||
|
|
0d030220c5 | ||
|
|
770e2b4a7b | ||
|
|
b2854a9010 | ||
|
|
0c68dd6b6b | ||
|
|
de8264d0e6 |
@@ -1,33 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Card} from 'react-bootstrap';
|
import {Card} from 'react-bootstrap';
|
||||||
|
import {getCardContent} from "./contentCard";
|
||||||
const descriptionDraw = (text) => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{text} <br/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CardCompact = ({item}) => {
|
const CardCompact = ({item}) => {
|
||||||
console.log(item);
|
const cc = getCardContent(item);
|
||||||
const image = item?.images?.length > 0 ? item.images[0]?.remoteUrl : null;
|
|
||||||
const rating = item.ratings?.imdb?.value ?? null;
|
|
||||||
const status = (item.monitored || item.hasFile) ? (
|
|
||||||
item.hasFile ? "Статус: Загружен" : "Статус: Загружается"
|
|
||||||
) : null;
|
|
||||||
return (
|
return (
|
||||||
<Card className="mb-3" style={{width: '18rem'}}>
|
<Card className="mb-auto mx-auto" style={{width: '20rem'}}>
|
||||||
{/*<Card.Img variant="top" src={item.remotePoster}/>*/}
|
<Card.Img variant="top" src={cc.image}/>
|
||||||
<Card.Img variant="top" src={image}/>
|
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>{item.title}</Card.Title>
|
<Card.Title>{item.title}</Card.Title>
|
||||||
<Card.Text>
|
<Card.Body>
|
||||||
{descriptionDraw(`Год: ${item.year}`)}
|
<div>Год: {item.year} <br/></div>
|
||||||
{descriptionDraw(`Рейтинг: ${rating}`)}
|
{cc.rating || cc.rating !== 0 ? <div>Рейтинг: {cc.rating} <br/></div> : null}
|
||||||
{item.sizeOnDisk ? descriptionDraw(`Размер: ${(item.sizeOnDisk / 1000000000).toFixed(2)} Gb`) : null}
|
{cc.status ? <div>{cc.status}<br/></div> : null}
|
||||||
{status ? descriptionDraw(status) : null}
|
{cc.size ? <div>Размер: {cc.size} Gb</div> : null}
|
||||||
</Card.Text>
|
{(item?.statistics?.seasonCount ? <div>Сезоны: {item.statistics.seasonCount}</div> : null)}
|
||||||
|
{cc.series ? <div>Серии: {cc.series}</div> : null}
|
||||||
|
</Card.Body>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Button, Card, Col, Row} from 'react-bootstrap';
|
import {Button, Card, Col, Row} from 'react-bootstrap';
|
||||||
|
import {getCardContent} from "./contentCard";
|
||||||
|
|
||||||
const CardExtended = ({item, selectHandle}) => {
|
const CardExtended = ({item, selectHandle}) => {
|
||||||
const rating = item.ratings?.imdb?.value ?? null;
|
const cc = getCardContent(item);
|
||||||
return (
|
return (
|
||||||
<Card className="mb-3" style={{width: '100%'}} onClick={() => selectHandle(item)}>
|
<Card className="mb-3" style={{width: '100%'}} onClick={() => selectHandle(item)}>
|
||||||
<Row>
|
<Row>
|
||||||
<Col md={3}>
|
<Col md={3}>
|
||||||
<Card.Img variant="top" src={item.remotePoster} style={{marginTop: '1rem'}}/>
|
<Card.Img variant="top" src={cc.image} style={{marginTop: '1rem'}}/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={9}>
|
<Col md={9}>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title>{item.title}</Card.Title>
|
<Card.Title>{item.title}</Card.Title>
|
||||||
<Card.Text>
|
<Card.Text>
|
||||||
<strong>Год:</strong> {item.year}<br/>
|
<strong>Год:</strong> {item.year}<br/>
|
||||||
<strong>Рейтинг:</strong> {rating}<br/>
|
<strong>Рейтинг:</strong> {cc.rating}<br/>
|
||||||
{item.overview}
|
{item.overview}
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
@@ -22,7 +23,7 @@ const CardExtended = ({item, selectHandle}) => {
|
|||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<Col style={{justifyContent: 'end'}}>
|
<Col style={{justifyContent: 'end'}}>
|
||||||
<Button variant={'outline-primary'} title={"Download"}>Загрузить</Button>
|
<Button variant={'outline-primary'} disabled title={"Download"}>{item.serial ? "Сериал" : "Фильм"}</Button>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Button, Col, Container, FormCheck, FormSelect, Modal, Row} from 'react-bootstrap';
|
import {Button, Col, Container, FormCheck, FormSelect, Modal, Row} from 'react-bootstrap';
|
||||||
import axios from "axios";
|
|
||||||
import {useToast} from "../hooks/useToast";
|
import {useToast} from "../hooks/useToast";
|
||||||
|
import {radarr, sonarr} from "../contexts/client";
|
||||||
const movieMonitor = [
|
import {movieMonitor, tvMonitor} from "./monitors";
|
||||||
{
|
import {createRequest} from "./saveRequest";
|
||||||
id: "movieAndCollection",
|
|
||||||
name: "Все части"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "movieOnly",
|
|
||||||
name: "Только этот фильм"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
||||||
const [movieQuality, setMovieQuality] = useState([])
|
const [movieQuality, setMovieQuality] = useState([])
|
||||||
@@ -20,59 +11,28 @@ const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
|||||||
const [monitor, setMonitor] = useState(null)
|
const [monitor, setMonitor] = useState(null)
|
||||||
const [film, setFilm] = useState(false)
|
const [film, setFilm] = useState(false)
|
||||||
const {addToast} = useToast();
|
const {addToast} = useToast();
|
||||||
|
const rating = item?.ratings?.imdb?.value ?? null;
|
||||||
|
const client = serial ? sonarr() : radarr();
|
||||||
|
const monitorArray = serial ? tvMonitor : movieMonitor;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/qualityprofile`,
|
client.get("api/v3/qualityprofile")
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then((r) => {
|
.then((r) => {
|
||||||
setMovieQuality(r.data)
|
setMovieQuality(r.data)
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch((err) => addToast(err, 'danger'));
|
||||||
|
// eslint-disable-next-lint
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
const request = !serial ? createMovieRequest : null;
|
createRequest(monitor, quality, item, serial, film, client)
|
||||||
request()
|
|
||||||
.then(res => {
|
.then(res => {
|
||||||
axios.post(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie`, res,
|
client.post(`api/v3/${serial ? "series" : "movie"}`, res)
|
||||||
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}})
|
|
||||||
.then(() => handleSave(item, serial))
|
.then(() => handleSave(item, serial))
|
||||||
.catch((err) => addToast(err, 'danger'));
|
.catch((err) => addToast(err.message, 'danger'));
|
||||||
|
// console.log(res)
|
||||||
})
|
})
|
||||||
.catch((err) => addToast(err, 'danger'));
|
.catch((err) => addToast(err.message, 'danger'));
|
||||||
}
|
|
||||||
|
|
||||||
const createMovieRequest = async () => {
|
|
||||||
if(!monitor) {
|
|
||||||
// eslint-disable-next-line no-throw-literal
|
|
||||||
throw 'Проверьте пункт отслеживания';
|
|
||||||
}
|
|
||||||
if(!quality) {
|
|
||||||
// eslint-disable-next-line no-throw-literal
|
|
||||||
throw 'Необходимо указать качество';
|
|
||||||
}
|
|
||||||
let request = item;
|
|
||||||
request.id = 0;
|
|
||||||
request.monitored = true;
|
|
||||||
request.qualityProfileId = quality;
|
|
||||||
request.minimumAvailability = "released"
|
|
||||||
request.addOptions = {
|
|
||||||
monitor: monitor,
|
|
||||||
searchForMovie: true
|
|
||||||
}
|
|
||||||
const folders = await axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/rootFolder`,
|
|
||||||
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}});
|
|
||||||
request.rootFolderPath = folders.data.find((d) => d.path.includes(film ? "film" : "mult")).path;
|
|
||||||
|
|
||||||
const tags = await axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/tag`,
|
|
||||||
{headers: {'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`}});
|
|
||||||
request.tags = tags.data.filter((t) => t.label === (film ? "film" : "mult")).map((t) => t.id)
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item) {
|
if (!item) {
|
||||||
@@ -84,25 +44,30 @@ const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
|||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>{item.title}</Modal.Title>
|
<Modal.Title>{item.title}</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body className="d-flex flex-column flex-md-row">
|
||||||
<Container fluid='xl'>
|
<Container fluid>
|
||||||
<Row>
|
<Row className="align-items-start">
|
||||||
<Col sm={{span: 3}}>
|
<Col md={3} className="mb-3">
|
||||||
<img width="100%" className='mb-3' src={item.remotePoster} alt={item.title}/>
|
<img width="100%" className='mb-3' src={item.remotePoster} alt={item.title}/>
|
||||||
</Col>
|
</Col>
|
||||||
<div className="col-9 container text-center">
|
<Col md={9} className="d-flex flex-column">
|
||||||
<Row>
|
<div>
|
||||||
<div className="col">
|
|
||||||
<p>{item.overview}</p>
|
<p>{item.overview}</p>
|
||||||
</div>
|
</div>
|
||||||
</Row>
|
<div>
|
||||||
<Row style={{width: '60%'}}>
|
<p>Год выпуска: {item.year}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<p>Рейтинг: {rating}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
<p style={{marginRight: '1rem'}}>Мультик</p>
|
<p style={{marginRight: '1rem'}}>Мультик</p>
|
||||||
<FormCheck type='switch' disabled={serial} style={{marginBottom: '1rem'}}
|
<FormCheck type='switch' disabled={serial} style={{marginBottom: '1rem'}}
|
||||||
onChange={() => setFilm(!film)}/>
|
onChange={() => setFilm(!film)}/>
|
||||||
<p>Фильм</p>
|
<p>Фильм</p>
|
||||||
<FormCheck type='checkbox' disabled={!serial}
|
<FormCheck type='checkbox' disabled checked={serial}
|
||||||
style={{marginBottom: '1rem', marginLeft: '1rem'}}
|
style={{marginBottom: '1rem', marginLeft: '1rem'}}
|
||||||
label="Сериал"/>
|
label="Сериал"/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -112,7 +77,7 @@ const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
|||||||
<p style={{width: '50%', paddingTop: '1rem'}}>Что отслеживать</p>
|
<p style={{width: '50%', paddingTop: '1rem'}}>Что отслеживать</p>
|
||||||
<FormSelect onChange={(e) => setMonitor(e.target.value)}>
|
<FormSelect onChange={(e) => setMonitor(e.target.value)}>
|
||||||
<option selected value={null}>Выберите пункт из меню</option>
|
<option selected value={null}>Выберите пункт из меню</option>
|
||||||
{movieMonitor.map((m) => <option value={m.id} key={m.id}>{m.name}</option>)}
|
{monitorArray.map((m) => <option value={m.id} key={m.id}>{m.name}</option>)}
|
||||||
</FormSelect>
|
</FormSelect>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
@@ -127,15 +92,15 @@ const RecommendationModal = ({show, handleClose, item, serial, handleSave}) => {
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Button variant={'primary'} onClick={() => handleSubmit()}>Скачать</Button>
|
|
||||||
</Container>
|
</Container>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button variant="secondary" onClick={() => handleClose()}>
|
<Button variant="secondary" onClick={() => handleClose()}>
|
||||||
Закрыть
|
Закрыть
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant="primary" onClick={() => handleSubmit()}>Скачать</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
export function SceletonCompact() {
|
|
||||||
return (
|
|
||||||
<div className="card" aria-hidden="true" style={{width:'18rem', height: '30rem'}}>
|
|
||||||
<div className="card-img-top" style={{background: 'grey', width: '18rem', height: '30rem'}} />
|
|
||||||
<div className="card-body">
|
|
||||||
<h5 className="card-title placeholder-glow">
|
|
||||||
<span className="placeholder col-6"></span>
|
|
||||||
</h5>
|
|
||||||
<p className="card-text placeholder-glow">
|
|
||||||
<span className="placeholder col-8"></span>
|
|
||||||
<span className="placeholder col-8"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
18
src/components/SkeletonCompact.js
Normal file
18
src/components/SkeletonCompact.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import {Card, CardBody, CardImg, CardText, CardTitle, Placeholder} from "react-bootstrap";
|
||||||
|
|
||||||
|
export function SkeletonCompact() {
|
||||||
|
return (
|
||||||
|
<Card className="mb-auto" aria-hidden="true" style={{width: '14rem', height: '30rem'}}>
|
||||||
|
<CardImg variant="top" style={{background: 'grey', width: '14rem', height: '30rem'}}/>
|
||||||
|
<CardBody>
|
||||||
|
<CardTitle className="placeholder-glow">
|
||||||
|
<Placeholder className="col-3"></Placeholder>
|
||||||
|
</CardTitle>
|
||||||
|
<CardText className="placeholder-glow">
|
||||||
|
<Placeholder className="col-6"></Placeholder>
|
||||||
|
<Placeholder className="col-6"></Placeholder>
|
||||||
|
</CardText>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
33
src/components/contentCard.js
Normal file
33
src/components/contentCard.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export const getCardContent = (item) => {
|
||||||
|
let result = {
|
||||||
|
rating: item.ratings?.imdb?.value ?? item.ratings.value ?? null,
|
||||||
|
status: null,
|
||||||
|
size: null,
|
||||||
|
series: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(item.images?.length > 0) {
|
||||||
|
result.image = item.images.find(image => image.coverType === "poster").remoteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.sizeOnDisk) {
|
||||||
|
result.size = item.sizeOnDisk / 1000000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.monitored || item.hasFile) {
|
||||||
|
result.status = item.hasFile ? "Статус: Загружен" : "Статус: Загружается"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.statistics) {
|
||||||
|
const stat = item.statistics
|
||||||
|
result.status = stat.percentOfEpisodes === 100 ? "Статус: Загружен" : "Статус: Загружается"
|
||||||
|
|
||||||
|
result.series = `${stat.episodeFileCount}/${stat.totalEpisodeCount}`
|
||||||
|
|
||||||
|
if (stat.sizeOnDisk) {
|
||||||
|
result.size = (stat.sizeOnDisk / 1000000000).toFixed(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
33
src/components/monitors.js
Normal file
33
src/components/monitors.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
export const movieMonitor = [
|
||||||
|
{
|
||||||
|
id: "movieAndCollection",
|
||||||
|
name: "Все части"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "movieOnly",
|
||||||
|
name: "Только этот фильм"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const tvMonitor = [
|
||||||
|
{
|
||||||
|
id: "all",
|
||||||
|
name: "Все серии"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "future",
|
||||||
|
name: "Новые серии"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "firstSeason",
|
||||||
|
name: "Первый сезон"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "lastSeason",
|
||||||
|
name: "Последний сезон"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "latestSeason",
|
||||||
|
name: "Последние сезоны"
|
||||||
|
},
|
||||||
|
]
|
||||||
49
src/components/saveRequest.js
Normal file
49
src/components/saveRequest.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
export const createRequest = async (monitor, quality, item, serial, film, client) => {
|
||||||
|
if (!monitor) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw 'Проверьте пункт отслеживания';
|
||||||
|
}
|
||||||
|
if (!quality) {
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw 'Необходимо указать качество';
|
||||||
|
}
|
||||||
|
let request = item;
|
||||||
|
request.monitored = true;
|
||||||
|
request.qualityProfileId = quality;
|
||||||
|
|
||||||
|
var addOptions = {
|
||||||
|
monitor: monitor,
|
||||||
|
}
|
||||||
|
|
||||||
|
const folders = await client.get("api/v3/rootFolder");
|
||||||
|
request.rootFolderPath = !serial ?
|
||||||
|
folders.data.find((d) => d.path.includes(film ? "film" : "mult")).path :
|
||||||
|
folders.data[0].path;
|
||||||
|
|
||||||
|
if (!serial) {
|
||||||
|
request.id = 0;
|
||||||
|
request.minimumAvailability = "released"
|
||||||
|
addOptions = {
|
||||||
|
...addOptions,
|
||||||
|
searchForMovie: true
|
||||||
|
}
|
||||||
|
const tags = await client.get("api/v3/tag");
|
||||||
|
request.tags = tags.data.filter((t) => t.label === (film ? "film" : "mult")).map((t) => t.id)
|
||||||
|
} else {
|
||||||
|
request.seasonFolder = true;
|
||||||
|
addOptions = {
|
||||||
|
...addOptions,
|
||||||
|
searchForMissingEpisodes: true,
|
||||||
|
searchForCutoffUnmetEpisodes: false
|
||||||
|
}
|
||||||
|
|
||||||
|
delete request.minimumAvailability;
|
||||||
|
delete request.monitorNewItems;
|
||||||
|
delete request.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete request.serial;
|
||||||
|
request.addOptions = addOptions;
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
23
src/contexts/client.js
Normal file
23
src/contexts/client.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const host = "tracker.kayashov.keenetic.pro";
|
||||||
|
|
||||||
|
export const radarr = () => {
|
||||||
|
const res = axios;
|
||||||
|
// res.defaults.baseURL = `http://192.168.1.100:30011/`;
|
||||||
|
res.defaults.baseURL = `${window.location.protocol}//${host}/radarr/`;
|
||||||
|
res.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
res.defaults.headers.common['X-Api-Key'] = process.env.REACT_APP_RADARR_API_KEY;
|
||||||
|
res.defaults.headers.common['Accept'] = 'application/json';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sonarr = () => {
|
||||||
|
const res = axios;
|
||||||
|
res.defaults.baseURL = `${window.location.protocol}//${host}/sonarr/`;
|
||||||
|
// res.defaults.baseURL = `http://192.168.1.100:30009/`;
|
||||||
|
res.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||||
|
res.defaults.headers.common['X-Api-Key'] = process.env.REACT_APP_SONARR_API_KEY;
|
||||||
|
res.defaults.headers.common['Accept'] = 'application/json';
|
||||||
|
return res;
|
||||||
|
}
|
||||||
@@ -1,23 +1,31 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import {Accordion, Badge, Col, Container, Row} from "react-bootstrap";
|
import {Accordion, Badge, Col, Container, Row} from "react-bootstrap";
|
||||||
import CardCompact from "../components/CardCompact";
|
import CardCompact from "../components/CardCompact";
|
||||||
import axios from "axios";
|
|
||||||
import {useToast} from "../hooks/useToast";
|
import {useToast} from "../hooks/useToast";
|
||||||
import {SceletonCompact} from "../components/SceletonCompact";
|
import {SkeletonCompact} from "../components/SkeletonCompact";
|
||||||
|
import {radarr, sonarr} from "../contexts/client";
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loadingTv, setLoadingTv] = useState(false);
|
||||||
|
const [loadingMovies, setLoadingMovies] = useState(false);
|
||||||
const [movies, setMovies] = useState([]);
|
const [movies, setMovies] = useState([]);
|
||||||
// const [series, setSeries] = useState([]);
|
const [tv, setTv] = useState([]);
|
||||||
const {addToast} = useToast();
|
const {addToast} = useToast();
|
||||||
|
|
||||||
|
// На будущую реализацию удаления
|
||||||
|
// api/v3/movie/68?deleteFiles=true&addImportExclusion=false
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoadingMovies(true);
|
||||||
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie`,
|
setLoadingTv(true);
|
||||||
{headers: {'X-Api-Key': process.env.REACT_APP_RADARR_API_KEY}})
|
radarr().get("api/v3/movie")
|
||||||
.then((r) => setMovies(r.data))
|
.then((r) => setMovies(r.data))
|
||||||
.catch((err) => addToast(err, 'alert'))
|
.catch((err) => addToast(err, 'alert'))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoadingMovies(false));
|
||||||
|
|
||||||
|
sonarr().get("api/v3/series")
|
||||||
|
.then((r) => setTv(r.data))
|
||||||
|
.catch((err) => addToast(err, 'alert'))
|
||||||
|
.finally(() => setLoadingTv(false));
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -29,17 +37,18 @@ const Home = () => {
|
|||||||
Фильмы
|
Фильмы
|
||||||
</Accordion.Header>
|
</Accordion.Header>
|
||||||
<Accordion.Body>
|
<Accordion.Body>
|
||||||
<Row className='row-cols-auto'>
|
<Row>
|
||||||
{loading ?
|
{loadingMovies ?
|
||||||
(
|
(
|
||||||
// Отображаем 10 скелетонов
|
// Отображаем 10 скелетонов
|
||||||
Array.from({length: 10}, (_, index) => (
|
Array.from({length: 10}, (_, index) => (
|
||||||
<Col key={`skeleton-${index}`}>
|
<Col key={`skeleton-${index}`}>
|
||||||
<SceletonCompact key={index} width={300} height={400}/>
|
<SkeletonCompact key={index} width={300} height={400}/>
|
||||||
</Col>
|
</Col>
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
: movies.map(rec => (
|
:
|
||||||
|
movies?.map(rec => (
|
||||||
<Col key={`col-${rec.title}-${rec.year}`}>
|
<Col key={`col-${rec.title}-${rec.year}`}>
|
||||||
<CardCompact item={rec} key={`${rec.title}-${rec.year}`}/>
|
<CardCompact item={rec} key={`${rec.title}-${rec.year}`}/>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -51,7 +60,22 @@ const Home = () => {
|
|||||||
<Accordion.Header className="mt-5">Сериалы</Accordion.Header>
|
<Accordion.Header className="mt-5">Сериалы</Accordion.Header>
|
||||||
<Accordion.Body>
|
<Accordion.Body>
|
||||||
<Row>
|
<Row>
|
||||||
<Badge bg="info">Функционал для сериалов пока не реализован</Badge>
|
{loadingTv ?
|
||||||
|
(
|
||||||
|
// Отображаем 10 скелетонов
|
||||||
|
Array.from({length: 10}, (_, index) => (
|
||||||
|
<Col key={`skeleton-tv-${index}`}>
|
||||||
|
<SkeletonCompact key={index} width={300} height={400}/>
|
||||||
|
</Col>
|
||||||
|
))
|
||||||
|
)
|
||||||
|
:
|
||||||
|
tv.length > 0 ? tv.map(rec => (
|
||||||
|
<Col key={`col-tv-${rec.title}-${rec.year}`}>
|
||||||
|
<CardCompact item={rec} key={`${rec.title}-${rec.year}`}/>
|
||||||
|
</Col>
|
||||||
|
)) :
|
||||||
|
<Badge bg="info">Функционал для сериалов пока не реализован</Badge>}
|
||||||
</Row>
|
</Row>
|
||||||
</Accordion.Body>
|
</Accordion.Body>
|
||||||
</Accordion.Item>
|
</Accordion.Item>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import React, {useEffect, useState} from 'react';
|
import React, {useEffect, useState} from 'react';
|
||||||
import axios from "axios";
|
|
||||||
import CardCompact from "../components/CardCompact";
|
import CardCompact from "../components/CardCompact";
|
||||||
import {SceletonCompact} from "../components/SceletonCompact";
|
import {SkeletonCompact} from "../components/SkeletonCompact";
|
||||||
import RecommendationModal from "../components/RecommendationModal";
|
import RecommendationModal from "../components/RecommendationModal";
|
||||||
import {Col, Container, Row} from "react-bootstrap";
|
import {Col, Container, Row} from "react-bootstrap";
|
||||||
import {useToast} from "../hooks/useToast";
|
import {useToast} from "../hooks/useToast";
|
||||||
|
import {radarr} from "../contexts/client";
|
||||||
|
|
||||||
export function Recommendation() {
|
export function Recommendation() {
|
||||||
const [recommendations, setRecommendations] = useState([]);
|
const [recommendations, setRecommendations] = useState([]);
|
||||||
@@ -15,14 +15,11 @@ export function Recommendation() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/importlist/movie?includeRecommendations=true&includeTrending=true&includePopular=true`,
|
radarr().get("api/v3/importlist/movie?includeRecommendations=true&includeTrending=true&includePopular=true")
|
||||||
{
|
.then((r) => setRecommendations(r.data))
|
||||||
headers: {
|
.catch((err) => addToast(err, 'danger'))
|
||||||
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
|
||||||
}
|
|
||||||
}).then((r) => setRecommendations(r.data))
|
|
||||||
.catch((err) => console.log(err))
|
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
|
// eslint-disable-next-lint
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function handleModal(item) {
|
function handleModal(item) {
|
||||||
@@ -45,13 +42,13 @@ export function Recommendation() {
|
|||||||
return (
|
return (
|
||||||
<Container className="text-center">
|
<Container className="text-center">
|
||||||
<h2 className="mt-3">Рекомендации</h2>
|
<h2 className="mt-3">Рекомендации</h2>
|
||||||
<Row className='row-cols-auto'>
|
<Row>
|
||||||
{loading ?
|
{loading ?
|
||||||
(
|
(
|
||||||
// Отображаем 10 скелетонов
|
// Отображаем 10 скелетонов
|
||||||
Array.from({length: 10}, (_, index) => (
|
Array.from({length: 10}, (_, index) => (
|
||||||
<Col key={`skeleton-${index}`}>
|
<Col key={`skeleton-${index}`}>
|
||||||
<SceletonCompact key={index} width={300} height={400}/>
|
<SkeletonCompact key={index} width={300} height={400}/>
|
||||||
</Col>
|
</Col>
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
@@ -61,7 +58,8 @@ export function Recommendation() {
|
|||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
<RecommendationModal show={modalView} handleClose={handleCloseModal} item={item} handleSave={handleDownload}/>
|
<RecommendationModal show={modalView} handleClose={handleCloseModal} item={item}
|
||||||
|
handleSave={handleDownload} serial={false}/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
import React, {useEffect, useMemo, useState} from 'react';
|
import React, {useEffect, useMemo, useState} from 'react';
|
||||||
import axios from "axios";
|
|
||||||
import CardExtend from "../components/CardExtend";
|
import CardExtend from "../components/CardExtend";
|
||||||
import {Alert, Container, InputGroup, Row} from "react-bootstrap";
|
import {Alert, Container, InputGroup, Row} from "react-bootstrap";
|
||||||
import RecommendationModal from "../components/RecommendationModal";
|
import RecommendationModal from "../components/RecommendationModal";
|
||||||
import {useToast} from "../hooks/useToast";
|
import {useToast} from "../hooks/useToast";
|
||||||
|
import {radarr, sonarr} from "../contexts/client";
|
||||||
|
|
||||||
export function Search() {
|
export function Search() {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
|
const [serial, setSerial] = useState(false);
|
||||||
const [loadMovies, setLoadMovies] = useState(false);
|
const [loadMovies, setLoadMovies] = useState(false);
|
||||||
// const [loadTv, setLoadTv] = useState(false);
|
const [loadTv, setLoadTv] = useState(false);
|
||||||
const [errorMovies, setErrorMovies] = useState(false);
|
const [resultTv, setResultTv] = useState([]);
|
||||||
// const [errorTv, setErrorTv] = useState(false);
|
|
||||||
// const [resultTv, setResultTv] = useState([]);
|
|
||||||
const [resultMovies, setResultMovies] = useState([]);
|
const [resultMovies, setResultMovies] = useState([]);
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [selectItem, setSelectItem] = useState({});
|
const [selectItem, setSelectItem] = useState({});
|
||||||
@@ -22,29 +21,44 @@ export function Search() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setLoadMovies(true);
|
setLoadMovies(true);
|
||||||
axios.get(`${process.env.REACT_APP_RADARR_HOST}/api/v3/movie/lookup?term=${query}}`,
|
radarr().get(`api/v3/movie/lookup?term=${query}}`)
|
||||||
{
|
.then((r) => {
|
||||||
headers: {
|
const movies = r.data.map((m) => {
|
||||||
// "Content-Type": "application/json",
|
return {
|
||||||
'X-Api-Key': `${process.env.REACT_APP_RADARR_API_KEY}`
|
...m,
|
||||||
|
serial: false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((r) => setResultMovies(r.data))
|
setResultMovies(movies);
|
||||||
.catch((err) => setErrorMovies(err))
|
})
|
||||||
|
.catch((err) => addToast(err, 'alert'))
|
||||||
.finally(() => setLoadMovies(false));
|
.finally(() => setLoadMovies(false));
|
||||||
|
|
||||||
|
setLoadTv(true);
|
||||||
|
sonarr().get(`api/v3/series/lookup?term=${query}}`)
|
||||||
|
.then((r) => {
|
||||||
|
const series = r.data.map((s) => {
|
||||||
|
return {
|
||||||
|
...s,
|
||||||
|
serial: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setResultTv(series)
|
||||||
|
}).catch((err) => addToast(err, 'alert'))
|
||||||
|
.finally(() => setLoadTv(false));
|
||||||
|
// eslint-disable-next-lin
|
||||||
}, [query]);
|
}, [query]);
|
||||||
|
|
||||||
const handleChange = (item) => {
|
const handleChange = (item) => {
|
||||||
setSelectItem(item);
|
setSelectItem(item);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
|
setSerial(item.serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = useMemo(() => {
|
const result = useMemo(() => {
|
||||||
let array = resultMovies;
|
let array = resultMovies.concat(resultTv);
|
||||||
// array.concat(resultTv);
|
|
||||||
return array.sort((a, b) => a.title.localeCompare(b.title));
|
return array.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
}, [resultMovies]);
|
}, [resultMovies, resultTv]);
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
@@ -55,15 +69,12 @@ export function Search() {
|
|||||||
let nState = resultMovies;
|
let nState = resultMovies;
|
||||||
nState = nState.filter(i => i.title !== item.title && i.year !== item.year);
|
nState = nState.filter(i => i.title !== item.title && i.year !== item.year);
|
||||||
|
|
||||||
const func = setResultMovies;
|
const func = serial ? setResultTv : setResultMovies;
|
||||||
func(nState);
|
func(nState);
|
||||||
addToast(`${item.title} добавлен в список загрузок`, 'success');
|
addToast(`${item.title} добавлен в список загрузок`, 'success');
|
||||||
handleCloseModal();
|
handleCloseModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Используем хук useAxios для обработки запросов
|
|
||||||
// const {response, loading, errorTv} = useAxios('/api/search', 'get', {query});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container style={{marginTop: 5}}>
|
<Container style={{marginTop: 5}}>
|
||||||
<h2>Поиск</h2>
|
<h2>Поиск</h2>
|
||||||
@@ -79,27 +90,25 @@ export function Search() {
|
|||||||
</InputGroup>
|
</InputGroup>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{loadMovies && <div className="text-center">Загрузка...</div>}
|
{(loadMovies || loadTv) && <div className="text-center">Загрузка...</div>}
|
||||||
|
|
||||||
{errorMovies && (
|
|
||||||
<Alert variant="danger">{errorMovies}</Alert>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{result && result.length > 0 && (
|
{result && result.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h3>Результаты поиска</h3>
|
<h3>Результаты поиска</h3>
|
||||||
<Row>
|
<Row>
|
||||||
{resultMovies.map(result => <CardExtend item={result} key={result.id} selectHandle={handleChange}/>)}
|
{result.map(result => <CardExtend item={result} key={result.id}
|
||||||
|
selectHandle={handleChange}/>)}
|
||||||
</Row>
|
</Row>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(!loadMovies && !errorMovies && (!resultMovies || !result.length)) && (
|
{((!loadMovies && (!resultMovies || !result.length) && (!loadTv && (!resultTv || !result.length))) &&
|
||||||
<Alert variant='info'>
|
<Alert variant='info'>
|
||||||
Ничего не найдено
|
Ничего не найдено
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<RecommendationModal item={selectItem} handleClose={handleCloseModal} show={showModal} serial={false} handleSave={handleSave}/>
|
<RecommendationModal item={selectItem} handleClose={handleCloseModal} show={showModal} serial={serial}
|
||||||
|
handleSave={handleSave}/>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user