posts page filter, sort, pagination
This commit is contained in:
parent
830459cdee
commit
e980a47515
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -5,8 +5,8 @@
|
|||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin Panel</title>
|
||||
<script type="module" crossorigin src="/assets/index.ebddc40f.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.727c4021.css">
|
||||
<script type="module" crossorigin src="/assets/index.3929b7af.js"></script>
|
||||
<link rel="stylesheet" href="/assets/index.0ae2b81c.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ button {
|
|||
button:disabled {
|
||||
background: #d4d4d4 !important;
|
||||
border: none !important;
|
||||
cursor: not-allowed;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,50 @@
|
|||
.posts {
|
||||
&__pagination {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 3rem;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
|
||||
input {
|
||||
width: 5%;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
font-size: 1.6rem;
|
||||
width: 15%;
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 0.5rem;
|
||||
background: #7c69ef;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
&__select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8rem;
|
||||
|
||||
label {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1.6rem;
|
||||
padding: 1rem 1.2rem;
|
||||
border: 1px solid #b8b9bb;
|
||||
border-radius: 0.2rem;
|
||||
cursor: default;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
select {
|
||||
padding: 1rem 1.2rem;
|
||||
border: 1px solid #b8b9bb;
|
||||
|
|
@ -22,9 +68,21 @@
|
|||
}
|
||||
|
||||
th {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 1.6rem;
|
||||
justify-self: center;
|
||||
background: transparent;
|
||||
color: #000;
|
||||
@include transition-std;
|
||||
|
||||
&.active {
|
||||
background: rgb(98, 98, 98);
|
||||
color: #fff;
|
||||
@include transition-std;
|
||||
}
|
||||
}
|
||||
|
||||
.post-link {
|
||||
|
|
@ -151,3 +209,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
&__empty {
|
||||
font-size: 1.6rem;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@ import React from "react";
|
|||
import { PostType } from "../types/posts";
|
||||
import { LinksAll } from "../types/links";
|
||||
|
||||
export const getPosts = (setPosts: React.Dispatch<PostType[]>) => {
|
||||
export const getPosts = (
|
||||
setPosts: React.Dispatch<PostType[]>,
|
||||
params?: string
|
||||
) => {
|
||||
axios
|
||||
.get("http://95.85.124.41:8080/posts")
|
||||
.get("http://95.85.124.41:8080/posts" + (params ? params : ""))
|
||||
.then((res) => {
|
||||
setPosts(res.data.data);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,34 +14,55 @@ import { Link } from "react-router-dom";
|
|||
import { parseDate } from "../helpers/parseDate";
|
||||
import { capitalizeFirstLetter } from "../helpers/stringMethods";
|
||||
|
||||
// Data
|
||||
const headers: string[] = [
|
||||
"ID",
|
||||
"Category",
|
||||
"Title",
|
||||
"Link",
|
||||
"Date",
|
||||
"Summary",
|
||||
];
|
||||
// Types
|
||||
import { paramsType } from "../types/posts";
|
||||
|
||||
interface pageType {
|
||||
perPage: number;
|
||||
pageNumber: number;
|
||||
}
|
||||
|
||||
const Posts = () => {
|
||||
const [categories, setCategories] = useState<string[]>(["All"]);
|
||||
const { posts, setPosts } = useContext<any>(PostContext);
|
||||
const [category, setCategory] = useState<string>("All");
|
||||
const [sort, setSort] = useState<string>("id");
|
||||
const [page, setPage] = useState<pageType>({
|
||||
perPage: 10,
|
||||
pageNumber: 1,
|
||||
});
|
||||
const [params, setParams] = useState<paramsType>({
|
||||
id: "asc",
|
||||
category: "asc",
|
||||
title: "asc",
|
||||
link: "asc",
|
||||
date: "asc",
|
||||
summary: "asc",
|
||||
});
|
||||
const [search, setSearch] = useState<string>("");
|
||||
|
||||
useEffect(() => {
|
||||
getPosts(setPosts);
|
||||
}, []);
|
||||
const key = sort as keyof typeof params;
|
||||
getPosts(
|
||||
setPosts,
|
||||
`?sortBy=${sort}.${params[key]}&strLimit=${page.perPage}&strOffset=${page.pageNumber}&filter=${search}`
|
||||
);
|
||||
}, [params, sort, page, search]);
|
||||
|
||||
useEffect(() => {
|
||||
const categoriesTemp: string[] = categories;
|
||||
if (posts[0].id !== -1) {
|
||||
posts.map((post: PostType) =>
|
||||
categoriesTemp.push(capitalizeFirstLetter(post.category.toLowerCase()))
|
||||
);
|
||||
let categoriesTempUnique = categoriesTemp.filter((element, index) => {
|
||||
return categoriesTemp.indexOf(element) === index;
|
||||
});
|
||||
setCategories(categoriesTempUnique);
|
||||
if (posts[0]) {
|
||||
if (posts[0].id !== -1) {
|
||||
posts.map((post: PostType) =>
|
||||
categoriesTemp.push(
|
||||
capitalizeFirstLetter(post.category.toLowerCase())
|
||||
)
|
||||
);
|
||||
let categoriesTempUnique = categoriesTemp.filter((element, index) => {
|
||||
return categoriesTemp.indexOf(element) === index;
|
||||
});
|
||||
setCategories(categoriesTempUnique);
|
||||
}
|
||||
}
|
||||
}, [posts]);
|
||||
|
||||
|
|
@ -53,40 +74,145 @@ const Posts = () => {
|
|||
<BsFillFileEarmarkPostFill className="dashboard__img" />
|
||||
<h1>Posts</h1>
|
||||
</div>
|
||||
<div className="posts_select">
|
||||
<select
|
||||
value={category}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setCategory(e.target.value);
|
||||
}}
|
||||
>
|
||||
{categories.map((category) => {
|
||||
if (category === "All") {
|
||||
return (
|
||||
<option key={uuidv4()} value={category} defaultChecked>
|
||||
{category}
|
||||
</option>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<option key={uuidv4()} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
<div className="posts__select__wrapper">
|
||||
<div className="posts__select">
|
||||
<label htmlFor="category">Category</label>
|
||||
<select
|
||||
id="category"
|
||||
value={category}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setCategory(e.target.value);
|
||||
}}
|
||||
>
|
||||
{categories.map((category) => {
|
||||
if (category === "All") {
|
||||
return (
|
||||
<option key={uuidv4()} value={category} defaultChecked>
|
||||
{category}
|
||||
</option>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<option key={uuidv4()} value={category}>
|
||||
{category}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</select>
|
||||
</div>
|
||||
<div className="posts__select">
|
||||
<label htmlFor="pp">Per page</label>
|
||||
<select
|
||||
id="pp"
|
||||
value={page.perPage}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setPage({ ...page, perPage: parseInt(e.target.value) });
|
||||
}}
|
||||
>
|
||||
<option value="10" defaultChecked>
|
||||
10
|
||||
</option>
|
||||
<option value="15" defaultChecked>
|
||||
15
|
||||
</option>
|
||||
<option value="20" defaultChecked>
|
||||
20
|
||||
</option>
|
||||
<option value="30" defaultChecked>
|
||||
30
|
||||
</option>
|
||||
<option value="40" defaultChecked>
|
||||
40
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="posts__select">
|
||||
<label htmlFor="filter">Filter</label>
|
||||
<input
|
||||
type="text"
|
||||
id="filter"
|
||||
value={search}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearch(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<table className="posts__table">
|
||||
<tbody>
|
||||
<tr className="posts__table__head">
|
||||
{headers.map((header: string) => {
|
||||
return <th key={uuidv4()}>{header}</th>;
|
||||
})}
|
||||
<th
|
||||
className={sort === "id" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("id");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, id: "asc" });
|
||||
else setParams({ ...params, id: "desc" });
|
||||
}}
|
||||
>
|
||||
ID
|
||||
</th>
|
||||
<th
|
||||
className={sort === "category" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("category");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, category: "asc" });
|
||||
else setParams({ ...params, category: "desc" });
|
||||
}}
|
||||
>
|
||||
Category
|
||||
</th>
|
||||
<th
|
||||
className={sort === "title" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("title");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, title: "asc" });
|
||||
else setParams({ ...params, title: "desc" });
|
||||
}}
|
||||
>
|
||||
Title
|
||||
</th>
|
||||
<th
|
||||
className={sort === "link" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("link");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, link: "asc" });
|
||||
else setParams({ ...params, link: "desc" });
|
||||
}}
|
||||
>
|
||||
Link
|
||||
</th>
|
||||
<th
|
||||
className={sort === "date" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("date");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, date: "asc" });
|
||||
else setParams({ ...params, date: "desc" });
|
||||
}}
|
||||
>
|
||||
Date
|
||||
</th>
|
||||
<th
|
||||
className={sort === "summary" ? "active" : ""}
|
||||
onClick={() => {
|
||||
setSort("summary");
|
||||
if (params.id === "asc")
|
||||
setParams({ ...params, summary: "asc" });
|
||||
else setParams({ ...params, summary: "desc" });
|
||||
}}
|
||||
>
|
||||
Summary
|
||||
</th>
|
||||
</tr>
|
||||
{posts[0].id !== -1
|
||||
? category === "All"
|
||||
? posts.map((post: PostType, index: number) => {
|
||||
{posts[0] ? (
|
||||
posts[0].id !== -1 ? (
|
||||
category === "All" ? (
|
||||
posts.map((post: PostType, index: number) => {
|
||||
return (
|
||||
<Link
|
||||
className="post-link"
|
||||
|
|
@ -94,7 +220,7 @@ const Posts = () => {
|
|||
key={uuidv4()}
|
||||
>
|
||||
<tr>
|
||||
<td>{index + 1}</td>
|
||||
<td>{post.id}</td>
|
||||
<td>
|
||||
{capitalizeFirstLetter(
|
||||
post.category.toLowerCase()
|
||||
|
|
@ -116,7 +242,8 @@ const Posts = () => {
|
|||
</Link>
|
||||
);
|
||||
})
|
||||
: posts.map((post: PostType, index: number) => {
|
||||
) : (
|
||||
posts.map((post: PostType, index: number) => {
|
||||
if (
|
||||
capitalizeFirstLetter(post.category.toLowerCase()) ===
|
||||
category
|
||||
|
|
@ -153,9 +280,38 @@ const Posts = () => {
|
|||
return "";
|
||||
}
|
||||
})
|
||||
: ""}
|
||||
)
|
||||
) : (
|
||||
""
|
||||
)
|
||||
) : (
|
||||
<td>
|
||||
<tr className="table__empty">No posts</tr>
|
||||
</td>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="posts__pagination">
|
||||
<button
|
||||
type="button"
|
||||
disabled={page.pageNumber === 1}
|
||||
onClick={() =>
|
||||
setPage({ ...page, pageNumber: page.pageNumber - 1 })
|
||||
}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
<input type="text" value={page.pageNumber} readOnly />
|
||||
<button
|
||||
disabled={posts[0] ? false : true}
|
||||
type="button"
|
||||
onClick={() =>
|
||||
setPage({ ...page, pageNumber: page.pageNumber + 1 })
|
||||
}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -8,3 +8,12 @@ export interface PostType {
|
|||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export interface paramsType {
|
||||
id: "asc" | "desc";
|
||||
category: "asc" | "desc";
|
||||
title: "asc" | "desc";
|
||||
link: "asc" | "desc";
|
||||
date: "asc" | "desc";
|
||||
summary: "asc" | "desc";
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue