histpry list, loader

This commit is contained in:
VividTruthKeeper 2022-09-20 15:12:51 +05:00
parent d9f9c8c013
commit 7c21815133
9 changed files with 242 additions and 71 deletions

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: transparent; display: block; shape-rendering: auto;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" fill="none" stroke="#2fad8c" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138">
<animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
</circle></svg>

After

Width:  |  Height:  |  Size: 597 B

View File

@ -0,0 +1,23 @@
.loader {
z-index: 20;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.4);
&.inner {
opacity: 1;
img {
width: 100%;
height: 100%;
max-width: 10rem;
max-height: 10rem;
}
}
}

View File

@ -17,6 +17,7 @@
button {
@include transition-std;
cursor: pointer;
color: $base-gray;
font-size: 1.6rem;
width: 15%;
padding: 1rem 2rem;
@ -35,6 +36,9 @@
gap: 0.8rem;
label {
display: flex;
gap: 0.8rem;
font-size: 1.4rem;
}
@ -67,6 +71,10 @@
}
&__table {
&.disabled {
pointer-events: none;
}
&__head {
min-height: unset !important;
background: $light-black;
@ -96,6 +104,10 @@
background: $light-blue;
td {
color: $light-black;
svg {
fill: $base-black;
}
}
}
}
@ -133,8 +145,10 @@
}
tbody {
max-height: 70vh;
overflow-y: auto;
min-height: 20rem;
position: relative;
// max-height: 70vh;
// overflow-y: auto;
display: flex;
flex-direction: column;
}
@ -211,6 +225,80 @@
font-size: 1.6rem;
}
}
&__table {
thead {
tr {
min-height: unset !important;
background: $light-black;
}
}
th {
cursor: pointer;
width: 100%;
height: 100%;
text-align: center;
font-size: 1.6rem;
justify-self: center;
background: transparent;
color: $base-gray;
@include transition-std;
}
tr {
background: $base-black;
&:nth-child(2n) {
background: $light-blue;
td {
color: $light-black;
svg {
fill: $base-black;
}
}
}
}
tr {
min-height: 7rem;
padding: 1rem;
display: grid;
grid-template-columns: 1fr repeat(4, 2fr);
gap: 1rem;
}
td {
color: $base-gray;
text-align: center;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
justify-self: center;
font-size: 1.2rem;
align-self: center;
a {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
svg {
width: 2rem;
height: 2rem;
}
}
tbody {
min-height: 20rem;
position: relative;
display: flex;
flex-direction: column;
}
}
}
}

View File

@ -9,3 +9,4 @@
@import "./details";
@import "./source";
@import "./edit-source";
@import "./loader.scss";

14
src/components/Loader.tsx Normal file
View File

@ -0,0 +1,14 @@
// Icons
import loader from "../assets/icons/loader.svg";
const Loader = () => {
return (
<div className={"loader"}>
<div className="loader inner">
<img src={loader} alt="" />
</div>
</div>
);
};
export default Loader;

View File

@ -7,15 +7,18 @@ import { PostType } from "../types/posts";
import { LinksAll } from "../types/links";
export const getPosts = (
setLoad: React.Dispatch<boolean>,
setPosts: React.Dispatch<PostType[]>,
params?: string
) => {
setLoad(true);
axios
.get("http://95.85.124.41:8080/posts" + (params ? params : ""))
.then((res) => {
setPosts(res.data.data);
})
.catch((err) => {});
.catch((err) => {})
.finally(() => setLoad(false));
};
export const deleteLink = (setSuccess: React.Dispatch<boolean>, id: number) => {

View File

@ -1,8 +1,9 @@
// Modules
import { v4 as uuidv4 } from "uuid";
import { useContext, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { PostContext } from "../context/PostContext";
import { PostType } from "../types/posts";
import { HistoryList, PostType } from "../types/posts";
import { IconContext } from "react-icons";
// Icons
@ -18,16 +19,7 @@ import { ContextType } from "../types/context";
const Post = () => {
const date = new Date("0.0.0000");
const [postData, setPostData] = useState<PostType>({
id: -1,
category: "",
title: "",
link: "",
publish_date: date,
summary: "",
createdAt: date,
updatedAt: date,
});
const [postData, setPostData] = useState<PostType>();
const { posts } = useContext<ContextType>(PostContext).postValue;
const { id } = useParams();
@ -48,18 +40,12 @@ const Post = () => {
<IconContext.Provider value={{ color: "#8DD77F" }}>
<FaBoxOpen className="dashboard__img" />
</IconContext.Provider>
<h1 className="post__head">
{postData.id !== -1 ? postData.title : ""}
</h1>
<h1 className="post__head">{postData ? postData.title : ""}</h1>
</div>
<div className="post__content">
<div className="post__content__block">
<h4>ID</h4>
<input
type="text"
readOnly
value={postData.id !== -1 ? postData.id : ""}
/>
<input type="text" readOnly value={postData ? postData.id : ""} />
</div>
<div className="post__content__block">
<h4>Category</h4>
@ -67,7 +53,7 @@ const Post = () => {
type="text"
readOnly
value={
postData.id !== -1
postData
? capitalizeFirstLetter(postData.category.toLowerCase())
: ""
}
@ -78,7 +64,7 @@ const Post = () => {
<input
type="text"
readOnly
value={postData.id !== -1 ? postData.title : ""}
value={postData ? postData.title : ""}
/>
</div>
<div className="post__content__block">
@ -86,9 +72,7 @@ const Post = () => {
<input
type={"text"}
readOnly
value={
postData.id !== -1 ? parseDate(postData.publish_date)[0] : ""
}
value={postData ? parseDate(postData.publish_date)[0] : ""}
/>
</div>
<div className="post__content__block">
@ -96,12 +80,12 @@ const Post = () => {
<textarea
readOnly
rows={5}
value={postData.id !== -1 ? postData.summary : ""}
value={postData ? postData.summary : ""}
></textarea>
</div>
<a
className="post__content__btn"
href={postData.id !== -1 ? postData.link : ""}
href={postData ? postData.link : ""}
>
<IconContext.Provider value={{ color: "#FFFFFF" }}>
<BiLinkExternal />
@ -109,6 +93,49 @@ const Post = () => {
<span>Link</span>
</a>
{postData ? (
postData.HistoryList.length > 0 ? (
<div className="post__content__block post__content__table">
<h2>History List</h2>
<table>
<thead>
<tr>
<th>Post ID</th>
<th>Old published</th>
<th>New published</th>
<th>Created</th>
<th>Updated</th>
</tr>
</thead>
<tbody>
{postData
? postData.HistoryList.length > 0
? postData.HistoryList.map((History: HistoryList) => {
return (
<tr key={uuidv4()}>
<td>{History.PostID}</td>
<td>
{parseDate(History.old_published_at)[0]}
</td>
<td>
{parseDate(History.new_published_at)[0]}
</td>
<td>{parseDate(History.createdAt)[0]}</td>
<td>{parseDate(History.updatedAt)[0]}</td>
</tr>
);
})
: ""
: ""}
</tbody>
</table>
</div>
) : (
""
)
) : (
""
)}
</div>
</div>
</div>

View File

@ -17,24 +17,31 @@ import { parseDate } from "../helpers/parseDate";
// Types
import { paramsType } from "../types/posts";
import { ContextType } from "../types/context";
// Components
import Loader from "../components/Loader";
interface pageType {
perPage: number;
pageNumber: number;
}
const Posts = () => {
const [categories, setCategories] = useState<string[]>(["All"]);
const [load, setLoad] = useState<boolean>(false);
const contextValue: ContextType = useContext<ContextType>(PostContext);
const { posts, setPosts } = contextValue.postValue;
const { sources } = contextValue.sourceValue;
const [showAll, setShowAll] = useState<boolean>(true);
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",
source: "asc",
title: "asc",
link: "asc",
date: "asc",
@ -47,29 +54,17 @@ const Posts = () => {
useEffect(() => {
const key = sort as keyof typeof params;
getPosts(
setLoad,
setPosts,
`?sortBy=${sort}.${params[key]}&strLimit=${page.perPage}&strOffset=${page.pageNumber}&filter=${search}`
);
}, [params, sort, page, search, category]);
}, [params, sort, page, search]);
useEffect(() => {
const categoriesTemp: string[] = categories;
if (posts[0]) {
if (posts[0].id !== -1) {
posts.map((post: PostType) => categoriesTemp.push(post.category));
let categoriesTempUnique = categoriesTemp.filter((element, index) => {
return categoriesTemp.indexOf(element) === index;
});
setCategories(categoriesTempUnique);
}
if (!showAll) {
setPage({ ...page, perPage: 60 });
}
}, [posts]);
useEffect(() => {
if (category !== "All") {
setPage({ ...page, perPage: 100 });
}
}, [category, page.perPage]);
}, [showAll, page.perPage]);
return (
<main className="posts">
@ -85,34 +80,40 @@ const Posts = () => {
<div className="posts__select">
<label htmlFor="category">Source</label>
<select
disabled={showAll}
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>
);
}
})}
{sources
? sources.map((source) => {
return (
<option key={uuidv4()} value={source.name}>
{source.name}
</option>
);
})
: ""}
</select>
<label>
<span>Show all</span>
<input
type="checkbox"
defaultChecked
value={showAll.toString()}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowAll(e.target.checked)
}
/>
</label>
</div>
<div className="posts__select">
<label htmlFor="pp">Per page</label>
<select
disabled={!showAll}
id="pp"
disabled={category !== "All"}
value={page.perPage}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
setPage({ ...page, perPage: parseInt(e.target.value) });
@ -142,7 +143,7 @@ const Posts = () => {
/>
</div>
</div>
<table className="posts__table">
<table className={load ? "posts__table disabled" : "posts__table"}>
<thead>
<tr className="posts__table__head">
<th
@ -159,10 +160,10 @@ const Posts = () => {
<th
className={sort === "category" ? "active" : ""}
onClick={() => {
setSort("category");
if (params.category !== "asc")
setParams({ ...params, category: "asc" });
else setParams({ ...params, category: "desc" });
setSort("source");
if (params.source !== "asc")
setParams({ ...params, source: "asc" });
else setParams({ ...params, source: "desc" });
}}
>
Source
@ -225,9 +226,10 @@ const Posts = () => {
</tr>
</thead>
<tbody>
{load ? <Loader /> : null}
{posts[0] ? (
posts[0].id !== -1 ? (
category === "All" ? (
showAll ? (
posts.map((post: PostType) => {
return (
<Link

View File

@ -1,3 +1,10 @@
export interface HistoryList {
old_published_at: Date;
new_published_at: Date;
PostID: number;
createdAt: Date;
updatedAt: Date;
}
export interface PostType {
id: number;
category: string;
@ -7,11 +14,12 @@ export interface PostType {
summary: string;
createdAt: Date;
updatedAt: Date;
HistoryList: HistoryList[];
}
export interface paramsType {
id: "asc" | "desc";
category: "asc" | "desc";
source: "asc" | "desc";
title: "asc" | "desc";
link: "asc" | "desc";
date: "asc" | "desc";