feat: nová stránka pro návrhy na vylepšení
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 38s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 38s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Successful in 40s
CI / Notify (push) Successful in 2s
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Button, OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faThumbsUp, faThumbsDown, faTrash, faPlus, faGear } from "@fortawesome/free-solid-svg-icons";
|
||||
import Header from "../components/Header";
|
||||
import Footer from "../components/Footer";
|
||||
import Loader from "../components/Loader";
|
||||
import { useAuth } from "../context/auth";
|
||||
import Login from "../Login";
|
||||
import AddSuggestionModal from "../components/modals/AddSuggestionModal";
|
||||
import SuggestionDetailModal from "../components/modals/SuggestionDetailModal";
|
||||
import {
|
||||
Suggestion,
|
||||
VoteDirection,
|
||||
listSuggestions,
|
||||
addSuggestion,
|
||||
voteSuggestion,
|
||||
deleteSuggestion,
|
||||
} from "../../../types";
|
||||
import "./SuggestionsPage.scss";
|
||||
|
||||
export default function SuggestionsPage() {
|
||||
const auth = useAuth();
|
||||
const [suggestions, setSuggestions] = useState<Suggestion[]>();
|
||||
const [addModalOpen, setAddModalOpen] = useState(false);
|
||||
const [detail, setDetail] = useState<Suggestion>();
|
||||
|
||||
const reload = useCallback(async () => {
|
||||
if (!auth?.login) return;
|
||||
const response = await listSuggestions();
|
||||
setSuggestions(response.data ?? []);
|
||||
}, [auth?.login]);
|
||||
|
||||
useEffect(() => {
|
||||
reload();
|
||||
}, [reload]);
|
||||
|
||||
const handleAdd = async (title: string, description: string) => {
|
||||
const response = await addSuggestion({ body: { title, description } });
|
||||
if (response.data) {
|
||||
setSuggestions(response.data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVote = async (id: string, direction: VoteDirection) => {
|
||||
const response = await voteSuggestion({ body: { id, direction } });
|
||||
if (response.data) {
|
||||
setSuggestions(response.data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (suggestion: Suggestion) => {
|
||||
if (!window.confirm(`Opravdu chcete smazat návrh „${suggestion.title}“? Smažou se i všechny jeho hlasy.`)) {
|
||||
return;
|
||||
}
|
||||
const response = await deleteSuggestion({ body: { id: suggestion.id } });
|
||||
if (response.data) {
|
||||
setSuggestions(response.data);
|
||||
}
|
||||
};
|
||||
|
||||
if (!auth?.login) {
|
||||
return <Login />;
|
||||
}
|
||||
|
||||
if (!suggestions) {
|
||||
return <Loader icon={faGear} description={"Načítám návrhy..."} animation={"fa-bounce"} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<div className="suggestions-page">
|
||||
<div className="suggestions-header">
|
||||
<h1>Návrhy na vylepšení</h1>
|
||||
<Button onClick={() => setAddModalOpen(true)}>
|
||||
<FontAwesomeIcon icon={faPlus} /> Přidat návrh
|
||||
</Button>
|
||||
</div>
|
||||
<p className="suggestions-info">
|
||||
Zde můžete navrhovat vylepšení aplikace a hlasovat o návrzích ostatních. U každého návrhu je
|
||||
zobrazeno jméno navrhovatele. Jména hlasujících jsou dostupná pouze administrátorům.
|
||||
</p>
|
||||
|
||||
{suggestions.length === 0 ? (
|
||||
<p className="suggestions-empty">Zatím nebyly přidány žádné návrhy. Buďte první!</p>
|
||||
) : (
|
||||
<table className="suggestions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Navrhovatel</th>
|
||||
<th>Název</th>
|
||||
<th className="col-score">Hlasy</th>
|
||||
<th className="col-actions">Akce</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{suggestions.map(suggestion => (
|
||||
<OverlayTrigger
|
||||
key={suggestion.id}
|
||||
placement="top"
|
||||
overlay={<Tooltip id={`tooltip-${suggestion.id}`}>{suggestion.description}</Tooltip>}
|
||||
>
|
||||
<tr onClick={() => setDetail(suggestion)}>
|
||||
<td>{suggestion.author}</td>
|
||||
<td>{suggestion.title}</td>
|
||||
<td className="col-score">{suggestion.voteScore}</td>
|
||||
<td className="col-actions" onClick={e => e.stopPropagation()}>
|
||||
<button
|
||||
type="button"
|
||||
className={`vote-btn vote-up ${suggestion.myVote === VoteDirection.UP ? "active" : ""}`}
|
||||
title="Hlasovat pro"
|
||||
onClick={() => handleVote(suggestion.id, VoteDirection.UP)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faThumbsUp} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`vote-btn vote-down ${suggestion.myVote === VoteDirection.DOWN ? "active" : ""}`}
|
||||
title="Hlasovat proti"
|
||||
onClick={() => handleVote(suggestion.id, VoteDirection.DOWN)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faThumbsDown} />
|
||||
</button>
|
||||
{suggestion.isMine && (
|
||||
<button
|
||||
type="button"
|
||||
className="vote-btn delete-btn"
|
||||
title="Smazat návrh"
|
||||
onClick={() => handleDelete(suggestion)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
</OverlayTrigger>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
<Footer />
|
||||
<AddSuggestionModal isOpen={addModalOpen} onClose={() => setAddModalOpen(false)} onSubmit={handleAdd} />
|
||||
<SuggestionDetailModal suggestion={detail} onClose={() => setDetail(undefined)} />
|
||||
<ToastContainer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user