Compare commits

..

No commits in common. "c793d802ef25cd6a84d71b2c5d1ce4b37a0c87c8" and "c7b8f30077e0bf73735e95974884f6e3f4950f8f" have entirely different histories.

16 changed files with 5397 additions and 10749 deletions

Binary file not shown.

11776
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,19 @@ import Contacts from './components/pages/Contacts';
import Catalogs from './components/pages/Catalogs'; import Catalogs from './components/pages/Catalogs';
import CatalogFilms from './components/catalogs/CatalogFilms'; import CatalogFilms from './components/catalogs/CatalogFilms';
import CatalogGenres from './components/catalogs/CatalogGenres'; import CatalogGenres from './components/catalogs/CatalogGenres';
import CatalogActors from './components/catalogs/CatalogActors'; import Reports from './components/pages/Reports';
import ReportsFilms from './components/reports/ReportsFilms';
const links = [ const links = [
{id: 1, text: "Главная", path: ""}, {id: 1, text: "Главная", path: ""},
{id: 2, text: "Фильмы", path: "films"}, {id: 2, text: "Фильмы", path: "films"},
{id: 3, text: "Контакты", path: "contacts"}, {id: 3, text: "Контакты", path: "contacts"},
{id: 4, text: "Каталог", path: "catalogs"}, {id: 4, text: "Каталог", path: "catalogs"},
{id: 5, text: "Отчеты", path: "reports"},
]; ];
function App() { function App() {
return ( return (
<> <>
@ -30,7 +34,8 @@ function App() {
<Route path="catalogs" element={<Catalogs/>}/> <Route path="catalogs" element={<Catalogs/>}/>
<Route path="catalogs/catalogFilms" element={<CatalogFilms />}/> <Route path="catalogs/catalogFilms" element={<CatalogFilms />}/>
<Route path="catalogs/catalogGenres" element={<CatalogGenres />}/> <Route path="catalogs/catalogGenres" element={<CatalogGenres />}/>
<Route path="catalogs/catalogActors" element={<CatalogActors />}/> <Route path="reports" element={<Reports />}/>
<Route path="reports/reportsFilms" element={<ReportsFilms />}/>
</Routes> </Routes>
</BrowserRouter> </BrowserRouter>
</> </>

View File

@ -1,51 +0,0 @@
import Catalog from "../common/Catalog";
import { useEffect, useState } from "react";
import DataService from "../../services/DataService";
let headers = [
{label: "name", text: "Имя"},
{label: "surname", text: "Фамилия"}
]
export default function CatalogActors() {
const url = "/actors";
const [data, setData] = useState({
name: "",
surname: ""
});
const [actors, setActors] = useState([]);
function handleFormChange(e) {
setData({ ...data, [e.target.id]: e.target.value })
}
function onAdd() {
setData({name: "", genre: actors[0]});
}
function onChange(data) {
setData(data);
}
function validate(data) {
if(data.name === "") return false;
return true;
}
return (
<Catalog url={url} headers={headers} data={data} onAdd={onAdd} onChange={onChange} validate={validate}>
<div className="mb-3">
<label htmlFor="name" className="form-label">Имя</label>
<input type="text" value={data.name} id="name" className="form-control" required autoComplete="off"
onChange={handleFormChange}/>
<label htmlFor="surname" className="form-label">Фамилия</label>
<input type="text" value={data.surname} id="surname" className="form-control" required autoComplete="off"
onChange={handleFormChange}/>
</div>
</Catalog>
)
}

View File

@ -5,8 +5,7 @@ import DataService from "../../services/DataService"
let headers = [ let headers = [
{label: "name", text: "Имя"}, {label: "name", text: "Имя"},
{label: "genre", text: "Жанр"}, {label: "genre", text: "Жанр"}
{label: "fullNames", text: "Актеры"}
] ]
@ -15,28 +14,15 @@ export default function CatalogFilms() {
const [data, setData] = useState({ const [data, setData] = useState({
name: "", name: "",
genre: [], genre: ""
fullNames: []
});
const [selectedData, setSelectedData] = useState({
genre: "",
actor: ""
}); });
const [genres, setGenres] = useState([]); const [genres, setGenres] = useState([]);
const [actors, setActors] = useState([]);
useEffect(() => { useEffect(() => {
let genre;
DataService.readAll("/genres").then(res => { DataService.readAll("/genres").then(res => {
setGenres(res) setGenres(res)
genre = res[0].name; setData({...data, genre: res[0].genre})
}).then(() => (
DataService.readAll("/actors")
)).then(res => {
setActors(res)
setSelectedData({genre, actor: res[0].name + " " + res[0].surname});
}); });
}, []) }, [])
@ -46,42 +32,8 @@ export default function CatalogFilms() {
setData({ ...data, [e.target.id]: e.target.value }) setData({ ...data, [e.target.id]: e.target.value })
} }
function handleChangeSelected(e) {
setSelectedData({...selectedData, [e.target.id]: e.target.value});
}
function addGenre() {
for(let g of data.genre) {
if(g == selectedData.genre) return;
}
let temp = data.genre.slice(0);
temp.push(selectedData.genre);
setData({...data, genre: temp});
}
function addActor() {
for(let a of data.fullNames) {
if(a == selectedData.actor) return;
}
let temp = data.fullNames.slice(0);
temp.push(selectedData.actor);
setData({...data, fullNames: temp});
}
function deleteGenre(e) {
let genreName = e.target.previousSibling.textContent;
let temp = data.genre.filter(x => (x != genreName));
setData({...data, genre: temp})
}
function deleteActor(e) {
let actorName = e.target.previousSibling.textContent;
let temp = data.fullNames.filter(x => (x != actorName));
setData({...data, fullNames: temp})
}
function onAdd() { function onAdd() {
setData({name: "", genre: [], fullNames: []}); setData({name: "", genre: genres[0].genre});
} }
@ -103,44 +55,14 @@ export default function CatalogFilms() {
onChange={handleFormChange}/> onChange={handleFormChange}/>
</div> </div>
<select id="genre" className="form-select" required <select id="genre" className="form-select" required
value={selectedData.genre} onChange={handleChangeSelected}> value={data.genre} onChange={handleFormChange}>
<option disabled value="">Укажите жанр</option> <option disabled value="">Укажите жанр</option>
{ {
genres.map(({name, id}) => genres.map(({genre, id}) =>
<option key={id} value={name}>{name}</option> <option key={id} value={genre}>{genre}</option>
) )
} }
</select> </select>
{
data.genre.map(value => (
<div className="badge bg-secondary m-1" key={value}>
<span>{value}</span>
<button className="btn-close bg-danger m-1" onClick={deleteGenre}></button>
</div>
))
}
<br></br>
<button onClick={addGenre} className="btn btn-success">Добавить жанр</button>
<select id="actor" className="form-select" required
value={selectedData.actor} onChange={handleChangeSelected}>
<option disabled value="">Укажите актера</option>
{
actors.map(({name, surname, id}) =>
<option key={id} value={name + " " + surname}>{name + " " + surname}</option>
)
}
</select>
{
data.fullNames.map(value => (
<div className="badge bg-secondary m-1" key={value}>
<span>{value}</span>
<button className="btn-close bg-danger m-1" onClick={deleteActor}></button>
</div>
))
}
<br></br>
<button onClick={addActor} className="btn btn-success">Добавить актера</button>
</div> </div>
</Catalog> </Catalog>
) )

View File

@ -4,14 +4,14 @@ import { useEffect, useState } from "react";
import DataService from "../../services/DataService" import DataService from "../../services/DataService"
let headers = [ let headers = [
{label: "name", text: "Жанр"} {label: "genre", text: "Жанр"}
] ]
export default function CatalogGenres() { export default function CatalogGenres() {
const url = "/genres"; const url = "/genres";
const [data, setData] = useState({ const [data, setData] = useState({
name: "" genre: ""
}); });
const [genres, setGenres] = useState([]); const [genres, setGenres] = useState([]);
@ -30,15 +30,15 @@ export default function CatalogGenres() {
} }
function validate(data) { function validate(data) {
if(data.name === "") return false; if(data.genre === "") return false;
return true; return true;
} }
return ( return (
<Catalog url={url} headers={headers} data={data} onAdd={onAdd} onChange={onChange} validate={validate}> <Catalog url={url} headers={headers} data={data} onAdd={onAdd} onChange={onChange} validate={validate}>
<div className="mb-3"> <div className="mb-3">
<label htmlFor="name" className="form-label">Название</label> <label htmlFor="genre" className="form-label">Название</label>
<input type="text" value={data.name} id="name" className="form-control" required autoComplete="off" <input type="text" value={data.genre} id="genre" className="form-control" required autoComplete="off"
onChange={handleFormChange}/> onChange={handleFormChange}/>
</div> </div>
</Catalog> </Catalog>

View File

@ -37,7 +37,7 @@ export default function Catalog(props) {
} }
function addHandler() { function addHandler() {
setModalHeader("Добавление"); setModalHeader("Добавление фильма");
setModalConfirm("Добавить"); setModalConfirm("Добавить");
setEdit(false); setEdit(false);
setVisible(true); setVisible(true);
@ -60,7 +60,7 @@ export default function Catalog(props) {
selectedItems = new Set(); selectedItems = new Set();
setModalHeader("Изменение"); setModalHeader("Изменение фильма");
setModalConfirm("Изменить"); setModalConfirm("Изменить");
setEdit(true); setEdit(true);
setVisible(true); setVisible(true);
@ -68,13 +68,11 @@ export default function Catalog(props) {
function removeHandler() { function removeHandler() {
let queries = []; let queries = [];
for(let id of selectedItems.values()) { for(let id of selectedItems.keys()) {
queries.push(DataService.remove(props.url, id)); queries.push(DataService.remove(props.url, id));
} }
Promise.all(queries).then(() => { Promise.all(queries).then(() => loadItems());
loadItems()
});
} }
function confirmHandler() { function confirmHandler() {

View File

@ -29,10 +29,9 @@ export default function Table(props) {
return ( return (
<tr key={elem.id} onClick={click.bind(this, elem.id)} className={trClass}> <tr key={elem.id} onClick={click.bind(this, elem.id)} className={trClass}>
<td>{index + 1}</td> <td>{index + 1}</td>
{props.headers.map(({label}) => { {props.headers.map(({label}) => (
return <td key={label}>{(Array.isArray(elem[label])) ? elem[label].join(", ") : elem[label]}</td> <td key={label}>{elem[label]}</td>
} ))}
)}
</tr> </tr>
)})} )})}
</tbody> </tbody>

View File

@ -2,8 +2,7 @@ import LinksList from "../common/LinksList";
export default function Catalogs() { export default function Catalogs() {
let items = [ let items = [
{text: "фильмы", link: "catalogFilms"},, {text: "фильмы", link: "catalogFilms"},
{text: "актеры", link: "catalogActors"},
{text: "жанры", link: "catalogGenres"} {text: "жанры", link: "catalogGenres"}
]; ];

View File

@ -1,10 +1,9 @@
export default class DataSirvice { export default class DataSirvice {
static urlPrefix = "http://localhost:8080"; static urlPrefix = "http://localhost:8079";
static async readAll(url) { static async readAll(url) {
let data = await fetch(this.urlPrefix + url); let data = await fetch(this.urlPrefix + url);
let res = await data.json(); return data.json();
return res;
} }
static create(url, data) { static create(url, data) {
@ -23,7 +22,7 @@ export default class DataSirvice {
console.log(data); console.log(data);
data.id = id; data.id = id;
return fetch(this.urlPrefix + url + "/" + id, { return fetch(this.urlPrefix + url + "/" + id, {
method: "PATCH", method: "PUT",
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@ -33,7 +32,7 @@ export default class DataSirvice {
} }
static remove(url, id) { static remove(url, id) {
return fetch(this.urlPrefix + url + "/" + id, { fetch(this.urlPrefix + url + "/" + id, {
method: "DELETE" method: "DELETE"
}); });
} }

View File

@ -32,7 +32,7 @@ public class ActorController {
return new ActorDTO(actorService.addActor(actor.getName(), actor.getSurname())); return new ActorDTO(actorService.addActor(actor.getName(), actor.getSurname()));
} }
@PatchMapping("/{id}") @PatchMapping("")
public ActorDTO updateActor(@PathVariable Long id, @RequestBody @Valid ActorDTO actor) { public ActorDTO updateActor(@PathVariable Long id, @RequestBody @Valid ActorDTO actor) {
return new ActorDTO(actorService.updateActor(id, actor.getName(), actor.getSurname())); return new ActorDTO(actorService.updateActor(id, actor.getName(), actor.getSurname()));
} }

View File

@ -49,7 +49,7 @@ public class FilmMvcController {
model.addAttribute("errors", bindingResult.getAllErrors()); model.addAttribute("errors", bindingResult.getAllErrors());
return "films-catalog"; return "films-catalog";
} }
Long filmId = id; Long filmId = filmDTO.getId();
if (id == null || id <= 0) { if (id == null || id <= 0) {
Film result = filmService.addFilm(filmDTO.getName()); Film result = filmService.addFilm(filmDTO.getName());

View File

@ -16,6 +16,7 @@ public class ActorDTO {
private String name; private String name;
private String surname; private String surname;
private String fullName; private String fullName;
private byte[] photo;
private List<Film> films; private List<Film> films;
@ -25,6 +26,7 @@ public class ActorDTO {
this.id = actor.getId(); this.id = actor.getId();
this.name = actor.getName(); this.name = actor.getName();
this.surname = actor.getSurname(); this.surname = actor.getSurname();
this.photo = actor.getPhoto();
this.fullName = this.name + this.surname; this.fullName = this.name + this.surname;
} }
public ActorDTO(Long id, String name, String surname, byte[] photo) { public ActorDTO(Long id, String name, String surname, byte[] photo) {
@ -32,6 +34,16 @@ public class ActorDTO {
this.name = name; this.name = name;
this.surname = surname; this.surname = surname;
this.fullName = this.name + this.surname; this.fullName = this.name + this.surname;
this.photo = photo;
}
public byte[] getPhoto() {
return photo;
}
public void setPhoto(String path) throws IOException {
File f = new File(path);
photo = Files.readAllBytes(f.toPath());
} }
public Long getId() { public Long getId() {

View File

@ -78,9 +78,6 @@
editForm.action = "/actor"; editForm.action = "/actor";
editForm.previousElementSibling.children[0].textContent = "Добавление"; editForm.previousElementSibling.children[0].textContent = "Добавление";
editForm.children[1].children[1].textContent = "Добавить"; editForm.children[1].children[1].textContent = "Добавить";
editForm.name.value = "";
editForm.surname.value = "";
} }
editBtn.onclick = () => { editBtn.onclick = () => {
@ -95,7 +92,6 @@
editForm.previousElementSibling.children[0].textContent = "Изменение"; editForm.previousElementSibling.children[0].textContent = "Изменение";
submitBtn.textContent = "Изменить"; submitBtn.textContent = "Изменить";
editForm.name.value = elem.children[1].textContent; editForm.name.value = elem.children[1].textContent;
editForm.surname.value = elem.children[2].textContent;
} }

View File

@ -44,34 +44,12 @@
<label htmlFor="nameInput" class="form-label">Название</label> <label htmlFor="nameInput" class="form-label">Название</label>
<input id="nameInput" type="text" th:field="${filmDTO.name}" name="name" class="form-control" required autoComplete="off"> <input id="nameInput" type="text" th:field="${filmDTO.name}" name="name" class="form-control" required autoComplete="off">
</div> </div>
<select name="selectedGenre" class="form-select"> <select name="genre" class="form-select" th:field="${filmDTO.genre}" multiple="multiple">
<option disabled value="">Укажите жанр</option>
<option th:each="g : ${allGenres}" <option th:each="g : ${allGenres}"
th:value="${g}" th:value="${g}"
th:text="${g}"></option> th:text="${g}"></option>
</select> </select>
<div id="selectedGenres"> <select name="fullNames" class="form-select" th:field="${filmDTO.fullNames}" multiple="multiple">
</div>
<br>
<button type="button" class="btn btn-success" id="addGenre">Добавить жанр</button>
<select name="selectedActor" class="form-select">
<option disabled value="">Укажите актера</option>
<option th:each="a : ${allActors}"
th:value="${a}"
th:text="${a}"></option>
</select>
<div id="selectedActors">
</div>
<br>
<button type="button" class="btn btn-success" id="addActor">Добавить актера</button>
<select name="genre" class="form-select" th:field="${filmDTO.genre}" multiple="multiple" hidden>
<option th:each="g : ${allGenres}"
th:value="${g}"
th:text="${g}"></option>
</select>
<select name="fullNames" class="form-select" th:field="${filmDTO.fullNames}" multiple="multiple" hidden>
<option th:each="a : ${allActors}" <option th:each="a : ${allActors}"
th:value="${a}" th:value="${a}"
th:text="${a}"></option> th:text="${a}"></option>
@ -96,8 +74,6 @@
let editBtn = document.getElementById("editBtn"); let editBtn = document.getElementById("editBtn");
let submitBtn = document.getElementById("submitBtn"); let submitBtn = document.getElementById("submitBtn");
let closeBtn = document.getElementById("closeBtn"); let closeBtn = document.getElementById("closeBtn");
let addGenreBtn = document.getElementById("addGenre");
let addActorBtn = document.getElementById("addActor");
let editForm = document.getElementById("edit-form"); let editForm = document.getElementById("edit-form");
let deleteForm = document.getElementById("delete-form"); let deleteForm = document.getElementById("delete-form");
@ -108,19 +84,7 @@
addBtn.onclick = (e) => { addBtn.onclick = (e) => {
editForm.action = "/film"; editForm.action = "/film";
editForm.previousElementSibling.children[0].textContent = "Добавление"; editForm.previousElementSibling.children[0].textContent = "Добавление";
submitBtn.textContent = "Добавить"; editForm.children[1].children[1].textContent = "Добавить";
editForm.name.value = "";
document.getElementById("selectedGenres").innerHTML = "";
for(let opt of editForm.genre.options) {
opt.selected = false;
}
document.getElementById("selectedActors").innerHTML = "";
for(let opt of editForm.fullNames.options) {
opt.selected = false;
}
} }
editBtn.onclick = (e) => { editBtn.onclick = (e) => {
@ -137,44 +101,8 @@
editForm.name.value = elem.children[1].textContent; editForm.name.value = elem.children[1].textContent;
let genresArr = elem.children[2].textContent.split(", "); let genresArr = elem.children[2].textContent.split(", ");
document.getElementById("selectedGenres").innerHTML = "";
for(let opt of editForm.genre.options) { for(let opt of editForm.genre.options) {
if(genresArr.indexOf(opt.value) == -1) continue; if(genresArr.indexOf(opt.value) != -1) opt.selected = true;
let budge = document.createElement("div");
budge.className = "badge bg-secondary m-1";
budge.innerHTML = `
<span>${opt.value}</span>
<button type="button" class="btn-close bg-danger m-1"></button>
`;
budge.children[1].onclick = () => {
budge.remove();
opt.selected = false;
}
document.getElementById("selectedGenres").appendChild(budge);
opt.selected = true;
}
let actorsArr = elem.children[3].textContent.split(", ");
document.getElementById("selectedActors").innerHTML = "";
for(let opt of editForm.fullNames.options) {
if(actorsArr.indexOf(opt.value) == -1) continue;
let budge = document.createElement("div");
budge.className = "badge bg-secondary m-1";
budge.innerHTML = `
<span>${opt.value}</span>
<button type="button" class="btn-close bg-danger m-1"></button>
`;
budge.children[1].onclick = () => {
budge.remove();
opt.selected = false;
}
document.getElementById("selectedActors").appendChild(budge);
opt.selected = true;
} }
} }
deleteBtn.onclick = () => { deleteBtn.onclick = () => {
@ -186,56 +114,6 @@
deleteForm.action = "/film/delete/" + elem.id; deleteForm.action = "/film/delete/" + elem.id;
deleteForm.submit(); deleteForm.submit();
} }
addGenreBtn.onclick = () => {
let selectedIndex = editForm.selectedGenre.selectedIndex;
let g = editForm.selectedGenre.options[selectedIndex].value;
for(let elem of editForm.genre.options) {
if(g != elem.value) continue;
if(elem.selected) return;
elem.selected = true;
let budge = document.createElement("div");
budge.className = "badge bg-secondary m-1";
budge.innerHTML = `
<span>${g}</span>
<button type="button" class="btn-close bg-danger m-1"></button>
`;
budge.children[1].onclick = () => {
budge.remove();
elem.selected = false;
}
document.getElementById("selectedGenres").appendChild(budge);
}
}
addActorBtn.onclick = () => {
let selectedIndex = editForm.selectedActor.selectedIndex;
let a = editForm.selectedActor.options[selectedIndex].value;
for(let elem of editForm.fullNames.options) {
if(a != elem.value) continue;
if(elem.selected) return;
elem.selected = true;
let budge = document.createElement("div");
budge.className = "badge bg-secondary m-1";
budge.innerHTML = `
<span>${a}</span>
<button type="button" class="btn-close bg-danger m-1"></button>
`;
budge.children[1].onclick = () => {
budge.remove();
elem.selected = false;
}
document.getElementById("selectedActors").appendChild(budge);
}
}
</script> </script>
</th:block> </th:block>
</body> </body>

View File

@ -71,7 +71,6 @@
editForm.action = "/genre"; editForm.action = "/genre";
editForm.previousElementSibling.children[0].textContent = "Добавление"; editForm.previousElementSibling.children[0].textContent = "Добавление";
editForm.children[1].children[1].textContent = "Добавить"; editForm.children[1].children[1].textContent = "Добавить";
editForm.name.value = "";
} }
editBtn.onclick = (e) => { editBtn.onclick = (e) => {