made about-us, news list, volunteers page and statistics page

This commit is contained in:
Alibek 2024-01-22 18:54:11 +06:00
parent 744dd7224c
commit 083ef42f9e
36 changed files with 1224 additions and 68 deletions

3
app/news/[id]/page.tsx Normal file
View File

@ -0,0 +1,3 @@
import NewsDetailsPage from "@/Pages/NewsDetailsPage/NewsDetailsPage";
export default NewsDetailsPage;

View File

@ -1,4 +1,13 @@
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = {}; const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
],
},
};
export default nextConfig; export default nextConfig;

View File

@ -0,0 +1,49 @@
@import "../../Shared/variables.scss";
.news-card {
min-height: 420px;
display: flex;
flex-direction: column;
gap: 24px;
img {
width: 100%;
height: 160px;
object-fit: cover;
}
&__date {
width: fit-content;
padding: 8px 12px;
background-color: $light-blue;
font-size: 14px;
font-weight: 500;
color: white;
border-radius: 3px;
}
&__text {
display: flex;
flex-direction: column;
flex: 1;
gap: 20px;
h4 {
font-size: 24px;
font-weight: 500;
}
p {
font-size: 16px;
font-weight: 400;
line-height: 150%;
}
}
&__more-btn {
width: fit-content;
padding: 8px 12px;
border: 2px solid #3b3b3b;
border-radius: 3px;
color: #3b3b3b;
font-weight: 600;
}
}

View File

@ -0,0 +1,57 @@
import Image, { StaticImageData } from "next/image";
import "./NewsCard.scss";
import Link from "next/link";
interface INewsCard {
id: number;
image: StaticImageData;
title: string;
description: string;
date: string;
}
const NewsCard: React.FC<INewsCard> = ({
id,
image,
title,
description,
date,
}: INewsCard) => {
const sliceDate = (date: string) => {
return `${date.slice(8, 10)}/${date.slice(5, 7)}/${date.slice(
0,
4
)}`;
};
const sliceDescription = (description: string) => {
if (description.length > 65) {
return `${description.slice(0, 65)}...`;
}
return description;
};
return (
<div className="news-card">
<Image
src={image}
alt="Card Image"
width={250}
height={160}
blurDataURL="blur"
/>
<div className="news-card__text">
<h5 className="news-card__date">{sliceDate(date)}</h5>
<h4>{title}</h4>
<p>{sliceDescription(description)}</p>
</div>
<Link href={`/news/${id}`} className="news-card__more-btn">
Подробнее
</Link>
</div>
);
};
export default NewsCard;

View File

@ -4,13 +4,15 @@ import search_icon from "./icons/search-icon.svg";
interface ISearchBar { interface ISearchBar {
placeholder?: string; placeholder?: string;
style?: object;
} }
const SearchBar: React.FC<ISearchBar> = ({ const SearchBar: React.FC<ISearchBar> = ({
placeholder, placeholder,
style,
}: ISearchBar) => { }: ISearchBar) => {
return ( return (
<div className="search-bar"> <div style={style} className="search-bar">
<div className="search-bar__input"> <div className="search-bar__input">
<Image src={search_icon} alt="Search Icon" /> <Image src={search_icon} alt="Search Icon" />
<input type="text" placeholder={placeholder} /> <input type="text" placeholder={placeholder} />

View File

@ -0,0 +1,100 @@
.about-us-page {
padding: 118px 90px 0px 90px;
display: flex;
flex-direction: column;
gap: 40px;
&__image {
display: flex;
justify-content: center;
margin-bottom: 25px;
img {
width: 100%;
height: 600px;
object-fit: cover;
border-radius: 12px;
}
}
&__container {
display: flex;
flex-direction: column;
gap: 10px;
h3 {
color: #3e3232;
font-size: 24px;
font-weight: 600;
}
div {
display: flex;
flex-direction: column;
gap: 30px;
p {
color: #3e3232;
font-feature-settings: "clig" off, "liga" off;
font-size: 20px;
font-weight: 500;
line-height: 34px;
}
}
}
}
@media screen and (max-width: 1024px) {
.about-us-page {
padding: 112px 30px 0px 30px;
}
}
@media screen and (max-width: 768px) {
.about-us-page {
padding: 112px 30px 0px 30px;
gap: 30px;
&__image {
margin-bottom: 0;
img {
height: 392px;
}
}
&__container {
h3 {
font-size: 20px;
}
div {
gap: 20px;
p {
font-size: 18px;
}
}
}
}
}
@media screen and (max-width: 550px) {
.about-us-page {
padding: 112px 16px 0px 16px;
gap: 20px;
&__image {
margin-bottom: 30px;
img {
height: 230px;
}
}
&__container {
div {
p {
font-size: 16px;
}
}
}
}
}

View File

@ -1,7 +1,66 @@
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
import "./AboutUsPage.scss"; import "./AboutUsPage.scss";
import Image from "next/image";
import image from "./assets/image.png";
const AboutUsPage = () => { const AboutUsPage = () => {
return <div>AboutUsPage</div>; return (
<div className="about-us-page">
<HeaderText>О нас</HeaderText>
<div className="about-us-page__image">
<Image src={image} alt="About Us Image" />
</div>
<div className="about-us-page__container">
<h3>Dont wait. The purpose of our lives is to be happy!</h3>
<div>
<p>
Upon arrival, your senses will be rewarded with the
pleasant scent of lemongrass oil used to clean the natural
wood found throughout the room, creating a relaxing
atmosphere within the space. A wonderful serenity has
taken possession of my entire soul, like these sweet
mornings of spring which I enjoy with my whole heart. I am
alone, and feel the charm of existence in this spot, which
was created for the bliss of souls like mine. I am so
happy, my dear friend, so absorbed in the exquisite.
</p>
<p>
When you are ready to indulge your sense of excitement,
check out the range of water- sports opportunities at the
resorts on-site water-sports center. Want to leave your
stress on the water? The resort has kayaks, paddleboards,
or the low-key pedal boats. Snorkeling equipment is
available as well, so you can experience the ever-changing
undersea environment. Not only do visitors to a bed and
breakfast get a unique perspective on the place they are
visiting, they have options for special packages not
available in other hotel settings.{" "}
</p>
<p>
bed and breakfasts can partner easily with local
businesses for a smoothly organized and highly
personalized vacation experience. The Fife and Drum Inn
offers options such as the Historic Triangle Package that
includes three nights at the Inn, breakfasts, and
admissions to historic Williamsburg, Jamestown, and
Yorktown. Bed and breakfasts also lend themselves to
romance.
</p>
<p>
Part of the charm of a bed and breakfast is the
uniqueness; art, décor, and food are integrated to create
a complete experience. For example, the Fife and Drum
retains the colonial feel of the area in all its guest
rooms. Special features include antique furnishings,
elegant four poster beds in some guest rooms, as well folk
art and artifacts from the restoration period of the
historic area available for guests to enjoy.
</p>
</div>
</div>
</div>
);
}; };
export default AboutUsPage; export default AboutUsPage;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@ -0,0 +1,7 @@
import "./NewsDetailsPage.scss";
const NewsDetailsPage = () => {
return <div>NewsDetailsPage</div>;
};
export default NewsDetailsPage;

View File

@ -0,0 +1,24 @@
.news-page {
padding: 118px 90px 0px 90px;
display: flex;
flex-direction: column;
gap: 35px;
}
@media screen and (max-width: 1024px) {
.news-page {
padding: 118px 30px 0px 30px;
}
}
@media screen and (max-width: 768px) {
.news-page {
padding: 112px 30px 0px 30px;
}
}
@media screen and (max-width: 550px) {
.news-page {
padding: 112px 16px 0px 16px;
}
}

View File

@ -1,7 +1,14 @@
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
import "./NewsPage.scss"; import "./NewsPage.scss";
import NewsList from "@/Widgets/NewsList/NewsList";
const NewsPage = () => { const NewsPage = () => {
return <div>NewsPage</div>; return (
<div className="news-page">
<HeaderText>Новости</HeaderText>
<NewsList />
</div>
);
}; };
export default NewsPage; export default NewsPage;

View File

@ -0,0 +1,24 @@
.statistics-page {
padding: 118px 90px 0 90px;
display: flex;
flex-direction: column;
gap: 40px;
}
@media screen and (max-width: 1024px) {
.statistics-page {
padding: 118px 30px 0 30px;
}
}
@media screen and (max-width: 768px) {
.statistics-page {
padding: 112px 30px 0 30px;
}
}
@media screen and (max-width: 1024px) {
.statistics-page {
padding: 112px 16px 0 16px;
}
}

View File

@ -1,7 +1,19 @@
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
import "./StatisticsPage.scss"; import "./StatisticsPage.scss";
import SearchBar from "@/Features/SearchBar/SearchBar";
import StatisticsTable from "@/Widgets/StatisticsTable/StatisticsTable";
const StatisticsPage = () => { const StatisticsPage = () => {
return <div>StatisticsPage</div>; return (
<div className="statistics-page">
<HeaderText>Статистика</HeaderText>
<SearchBar
style={{ width: "100%" }}
placeholder="Введите населенный пункт"
/>
<StatisticsTable />
</div>
);
}; };
export default StatisticsPage; export default StatisticsPage;

View File

@ -0,0 +1,24 @@
.volunteers-page {
padding: 118px 90px 0 90px;
display: flex;
flex-direction: column;
gap: 40px;
}
@media screen and (max-width: 1024px) {
.volunteers-page {
padding: 118px 30px 0 30px;
}
}
@media screen and (max-width: 768px) {
.volunteers-page {
padding: 112px 30px 0 30px;
}
}
@media screen and (max-width: 550px) {
.volunteers-page {
padding: 112px 16px 0 16px;
}
}

View File

@ -1,7 +1,14 @@
import HeaderText from "@/Shared/UI/HeaderText/HeaderText";
import "./VolunteersPage.scss"; import "./VolunteersPage.scss";
import VolunteersTable from "@/Widgets/VolunteersTable/VolunteersTable";
const VolunteersPage = () => { const VolunteersPage = () => {
return <div>VolunteersPage</div>; return (
<div className="volunteers-page">
<HeaderText>Волонтеры</HeaderText>
<VolunteersTable />
</div>
);
}; };
export default VolunteersPage; export default VolunteersPage;

View File

@ -0,0 +1,19 @@
@import "../../variables.scss";
.header-text {
color: $gray-700;
font-size: 42px;
font-weight: 500;
}
@media screen and (max-width: 768px) {
.header-text {
font-size: 36px;
}
}
@media screen and (max-width: 550px) {
.header-text {
font-size: 24px;
}
}

View File

@ -0,0 +1,13 @@
import "./HeaderText.scss";
interface IHeaderText {
children?: React.ReactNode;
}
const HeaderText: React.FC<IHeaderText> = ({
children,
}: IHeaderText) => {
return <h2 className="header-text">{children}</h2>;
};
export default HeaderText;

View File

@ -4,3 +4,9 @@ export interface IFetch {
loading: boolean; loading: boolean;
error?: string; error?: string;
} }
export interface IList {
count: number;
next: number | null;
previous: number | null;
}

View File

@ -0,0 +1,17 @@
.news-list {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 20px;
}
@media screen and (max-width: 1024px) {
.news-list {
grid-template-columns: 1fr 1fr;
}
}
@media screen and (max-width: 550px) {
.news-list {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,31 @@
"use client";
import "./NewsList.scss";
import { useEffect } from "react";
import { useNews } from "./news.store";
import NewsCard from "@/Entities/NewsCard/NewsCard";
const NewsList = () => {
const { data, getNews } = useNews();
useEffect(() => {
getNews();
}, []);
return (
<ul className="news-list">
{data.results.map((card) => (
<li key={card.id}>
<NewsCard
id={card.id}
image={card.image}
title={card.title}
description={card.description}
date={card.created_at}
/>
</li>
))}
</ul>
);
};
export default NewsList;

View File

@ -0,0 +1,45 @@
import { baseAPI } from "@/Shared/API/baseAPI";
import { IFetch, IList } from "@/Shared/types";
import axios from "axios";
import { StaticImageData } from "next/image";
import { create } from "zustand";
interface INews extends IList {
results: IResult[];
}
interface IResult {
id: number;
image: StaticImageData;
title: string;
description: string;
created_at: string;
}
interface INewsStore extends IFetch {
data: INews;
getNews: () => Promise<void>;
}
export const useNews = create<INewsStore>((set) => ({
data: {
count: 0,
next: null,
previous: null,
results: [],
},
loading: false,
error: "",
getNews: async () => {
try {
set({ loading: true });
const response = await axios.get<INews>(`${baseAPI}/news/`);
set({ data: response.data });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
}));

View File

@ -0,0 +1,103 @@
@import "../../Shared/variables.scss";
.statistics-table {
position: relative;
&__wrapper {
overflow: hidden;
overflow-x: auto;
border-radius: 6px;
border: 1px solid #d5d5d5;
}
&__location {
position: absolute;
top: 30%;
left: 1%;
border-radius: 8px;
z-index: 15;
li {
background-color: #fff;
button {
width: 100%;
padding: 14px;
color: #101828;
font-size: 14px;
font-weight: 400;
}
}
li:hover {
background-color: #f1f1f1;
}
&_active {
background-color: #f1f1f1;
}
}
table {
width: 100%;
border-collapse: collapse;
thead {
height: 76px;
tr {
background-color: #f4f4f4;
th {
border-style: none;
padding: 0 20px;
color: $gray-500;
font-size: 14px;
font-weight: 500;
text-align: start;
cursor: pointer;
.statistics-table__header {
display: flex;
align-items: center;
gap: 2px;
div {
display: flex;
align-items: center;
}
}
}
th:first-child {
padding-left: 20px;
}
}
}
tbody {
tr {
height: 76px;
border-bottom: 1px solid #f1f4f9;
td {
padding: 0 20px;
color: $gray-500;
font-size: 20px;
font-weight: 500;
width: 100px;
}
td:first-child {
padding-left: 20px;
color: black;
}
}
tr:last-child {
border: none;
}
}
}
}

View File

@ -0,0 +1,189 @@
"use client";
import "./StatisticsTable.scss";
import Image from "next/image";
import arrow_down_icon from "./icons/arrow-down-icon.svg";
import arrow_up_icon from "./icons/arrow-up-icon.svg";
import Link from "next/link";
import chevron_down from "./icons/chevron-down.svg";
import { useStatistics } from "./statistics.store";
import { useEffect, useState } from "react";
const StatisticsTable = () => {
const [location, setLocation] = useState("city");
const [locationMenu, setLocationMenu] = useState(false);
const {
data,
getStatisticsForCity,
getStatisticsForState,
getStatisticsForVillage,
} = useStatistics();
useEffect(() => {
getStatisticsForState();
}, []);
return (
<div className="statistics-table">
{locationMenu && (
<ul
onClick={(e) => e.stopPropagation()}
className="statistics-table__location"
>
<li
className={
location === "state"
? "statistics-table__location_active"
: ""
}
>
<button
onClick={() => {
setLocation("state");
getStatisticsForState();
}}
>
Регион
</button>
</li>
<li
className={
location === "city"
? "statistics-table__location_active"
: ""
}
>
<button
onClick={() => {
setLocation("city");
getStatisticsForCity();
}}
>
Город
</button>
</li>
<li
className={
location === "village"
? "statistics-table__location_active"
: ""
}
>
<button
onClick={() => {
setLocation("village");
getStatisticsForVillage();
}}
>
Деревня
</button>
</li>
</ul>
)}
<div className="statistics-table__wrapper">
<table cellSpacing={0}>
<thead>
<tr>
<th
tabIndex={1}
onClick={() => setLocationMenu((prev) => !prev)}
>
<div>
Город
<Image src={chevron_down} alt="Chevron Icon" />
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
Добавлено дорог
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
Локальных дефектов
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
Очагов аварийности
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
Локальных дефектов исправлено
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
В планах ремонта
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
<th tabIndex={1}>
<div className="statistics-table__header">
Отремонтировано
<div>
<Image
src={arrow_down_icon}
alt="Arrow Down Icon"
/>
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</div>
</th>
</tr>
</thead>
<tbody>
{data.map((statistic) => (
<tr>
<td>{statistic.name}</td>
<td>{statistic.broken_road_1}</td>
<td>{statistic.local_defect_3}</td>
<td>{statistic.hotbed_of_accidents_2}</td>
<td>{statistic.local_defect_fixed_6}</td>
<td>{statistic.repair_plans_4}</td>
<td>{statistic.repaired_5}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default StatisticsTable;

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-down">
<path id="Icon" d="M8.00016 3.33301V12.6663M8.00016 12.6663L12.6668 7.99967M8.00016 12.6663L3.3335 7.99967" stroke="#667085" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-down">
<path id="Icon" d="M7.99984 12.667L7.99984 3.33366M7.99984 3.33366L3.33317 8.00033M7.99984 3.33366L12.6665 8.00033" stroke="#667085" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@ -0,0 +1,5 @@
<svg width="18" height="12" viewBox="0 0 18 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow/chevron-down">
<path id="Vector" d="M12.7503 4H5.25034C5.1077 3.99993 4.96799 4.04055 4.84761 4.11708C4.72723 4.19362 4.63118 4.30289 4.57071 4.43209C4.51025 4.56129 4.48788 4.70505 4.50624 4.84651C4.52459 4.98797 4.58291 5.12126 4.67434 5.23075L8.42435 9.73075C8.49473 9.81516 8.58281 9.88306 8.68235 9.92966C8.78188 9.97626 8.89044 10.0004 9.00035 10.0004C9.11025 10.0004 9.21881 9.97626 9.31834 9.92966C9.41788 9.88306 9.50596 9.81516 9.57634 9.73075L13.3263 5.23075C13.4178 5.12126 13.4761 4.98797 13.4945 4.84651C13.5128 4.70505 13.4904 4.56129 13.43 4.43209C13.3695 4.30289 13.2735 4.19362 13.1531 4.11708C13.0327 4.04055 12.893 3.99993 12.7503 4ZM9.00035 8.0785L6.8516 5.5H11.1491L9.00035 8.0785Z" fill="#667085"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 843 B

View File

@ -0,0 +1,72 @@
import { baseAPI } from "@/Shared/API/baseAPI";
import { IFetch } from "@/Shared/types";
import axios from "axios";
import { create } from "zustand";
interface IStatistic {
name: string;
broken_road_1: number;
hotbed_of_accidents_2: number;
local_defect_3: number;
repair_plans_4: number;
repaired_5: number;
local_defect_fixed_6: number;
}
interface IStatisticStore extends IFetch {
data: IStatistic[];
getStatisticsForCity: () => Promise<void>;
getStatisticsForState: () => Promise<void>;
getStatisticsForVillage: () => Promise<void>;
}
export const useStatistics = create<IStatisticStore>((set) => ({
data: [],
loading: false,
error: "",
getStatisticsForCity: async () => {
try {
set({ loading: true });
const response = await axios.get<IStatistic[]>(
`${baseAPI}/report/city/stats/`
);
set({ data: response.data });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
getStatisticsForState: async () => {
try {
set({ loading: true });
const response = await axios.get<IStatistic[]>(
`${baseAPI}/report/state/stats/`
);
set({ data: response.data });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
getStatisticsForVillage: async () => {
try {
set({ loading: true });
const response = await axios.get<IStatistic[]>(
`${baseAPI}/report/village/stats/`
);
set({ data: response.data });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
}));

View File

@ -0,0 +1,140 @@
@import "../../Shared/variables.scss";
.volunteers-table {
overflow: hidden;
overflow-x: auto;
border-radius: 6px;
border: 1px solid var(--Grey-for-stroke, #d5d5d5);
table {
width: 100%;
border-collapse: collapse;
thead {
height: 76px;
tr {
background-color: #f4f4f4;
th {
border-style: none;
color: $gray-500;
font-size: 16px;
font-weight: 500;
text-align: start;
div {
display: flex;
align-items: center;
gap: 4px;
}
}
th:first-child {
padding-left: 20px;
min-width: 113px;
}
th:nth-child(2) {
min-width: 206px;
}
th:nth-child(3) {
cursor: pointer;
min-width: 190px;
}
th:nth-child(4) {
cursor: pointer;
min-width: 213px;
}
th:nth-child(5) {
cursor: pointer;
min-width: 222px;
}
th:last-child {
cursor: pointer;
min-width: 92px;
}
}
}
tbody {
tr {
height: 76px;
border-bottom: 1px solid #f1f4f9;
td {
color: $gray-500;
font-size: 20px;
font-weight: 500;
}
td:first-child {
padding-left: 20px;
min-width: 113px;
border-top-left-radius: 6px;
}
td:nth-child(2) {
min-width: 206px;
a {
color: $light-blue;
text-decoration: underline;
}
}
td:nth-child(3) {
min-width: 190px;
}
td:nth-child(4) {
min-width: 213;
}
td:nth-child(5) {
min-width: 222px;
}
td:last-child {
min-width: 92px;
border-top-right-radius: 6px;
}
}
tr:last-child {
border: none;
}
}
}
}
@media screen and (max-width: 550px) {
.volunteers-table {
table {
tbody {
tr {
td:first-child {
padding-left: 20px;
min-width: 113px;
border-top-left-radius: 6px;
}
td:nth-child(2) {
min-width: 206px;
a {
color: $light-blue;
text-decoration: underline;
}
}
td:nth-child(3) {
min-width: 190px;
}
td:nth-child(4) {
min-width: 213;
}
td:nth-child(5) {
min-width: 222px;
}
td:last-child {
min-width: 92px;
border-top-right-radius: 6px;
}
}
}
}
}
}

View File

@ -0,0 +1,75 @@
"use client";
import Image from "next/image";
import "./VolunteersTable.scss";
import arrow_down_icon from "./icons/arrow-down-icon.svg";
import arrow_up_icon from "./icons/arrow-up-icon.svg";
import Link from "next/link";
import { useVolunteers } from "./volunteer.store";
import { useEffect } from "react";
const VolunteersTable = () => {
const { getVolunteers, data } = useVolunteers();
useEffect(() => {
getVolunteers();
}, []);
return (
<div className="volunteers-table">
<table cellSpacing={0}>
<thead>
<tr>
<th></th>
<th>Активист</th>
<th>
<div>
Добавлено дорог
<Image src={arrow_down_icon} alt="Arrow Down Icon" />
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</th>
<th>
<div>
Получено голосов{" "}
<Image src={arrow_down_icon} alt="Arrow Down Icon" />
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</th>
<th>
<div>
Оставлено голосов{" "}
<Image src={arrow_down_icon} alt="Arrow Down Icon" />
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</th>
<th>
<div>
Рейтинг{" "}
<Image src={arrow_down_icon} alt="Arrow Down Icon" />
<Image src={arrow_up_icon} alt="Arrow Up Icon" />
</div>
</th>
</tr>
</thead>
<tbody>
{data.map((volunteer) => (
<tr key={volunteer.user_id}>
<td>1</td>
<td>
<Link href={"#"}>{volunteer.username}</Link>
</td>
<td>{volunteer.report_count}</td>
<td>{volunteer.likes_received_count}</td>
<td>{volunteer.likes_given_count}</td>
<td>{volunteer.average_rating}</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
export default VolunteersTable;

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-down">
<path id="Icon" d="M8.00016 3.33301V12.6663M8.00016 12.6663L12.6668 7.99967M8.00016 12.6663L3.3335 7.99967" stroke="#667085" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 325 B

View File

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="arrow-down">
<path id="Icon" d="M7.99984 12.667L7.99984 3.33366M7.99984 3.33366L3.33317 8.00033M7.99984 3.33366L12.6665 8.00033" stroke="#667085" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 333 B

View File

@ -0,0 +1,39 @@
import { baseAPI } from "@/Shared/API/baseAPI";
import { IFetch, IList } from "@/Shared/types";
import axios from "axios";
import { create } from "zustand";
interface IVolunteer {
user_id: number;
username: string;
report_count: number;
likes_given_count: number;
likes_received_count: number;
average_rating: number;
}
interface IVolunteerStore extends IFetch {
data: IVolunteer[];
getVolunteers: () => Promise<void>;
}
export const useVolunteers = create<IVolunteerStore>((set) => ({
data: [],
loading: false,
error: "",
getVolunteers: async () => {
try {
set({ loading: true });
const response = await axios.get<IVolunteer[]>(
`${baseAPI}/report/user_ratings/`
);
set({ data: response.data });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
}));

View File

@ -14,52 +14,6 @@
gap: 30px; gap: 30px;
} }
.news-card {
display: flex;
flex-direction: column;
gap: 24px;
img {
width: 100%;
height: 160px;
object-fit: cover;
}
&__date {
width: fit-content;
padding: 8px 12px;
background-color: $light-blue;
font-size: 14px;
font-weight: 500;
color: white;
border-radius: 3px;
}
&__text {
display: flex;
flex-direction: column;
gap: 20px;
h4 {
font-size: 24px;
font-weight: 500;
}
p {
font-size: 16px;
font-weight: 400;
line-height: 150%;
}
}
&__more-btn {
width: fit-content;
padding: 8px 12px;
border: 2px solid #3b3b3b;
border-radius: 3px;
color: #3b3b3b;
font-weight: 600;
}
}
&__read-more-btn { &__read-more-btn {
width: fit-content; width: fit-content;
display: flex; display: flex;

View File

@ -1,32 +1,36 @@
"use client";
import "./NewsSection.scss"; import "./NewsSection.scss";
import Image from "next/image"; import Image from "next/image";
import SectionHeader from "@/Shared/UI/SectionHeader/SectionHeader"; import SectionHeader from "@/Shared/UI/SectionHeader/SectionHeader";
import image from "./assets/image.jpg"; import image from "./assets/image.jpg";
import arrow_icon from "./icons/arrow-right-icon.svg"; import arrow_icon from "./icons/arrow-right-icon.svg";
import Link from "next/link"; import Link from "next/link";
import { useNewsHome } from "./news-home.store";
import { useEffect } from "react";
import NewsCard from "@/Entities/NewsCard/NewsCard";
const NewsSection = () => { const NewsSection = () => {
const { data, loading, error, getNews } = useNewsHome();
useEffect(() => {
getNews();
}, []);
return ( return (
<div className="news-section"> <div className="news-section">
<SectionHeader>Новости</SectionHeader> <SectionHeader>Новости</SectionHeader>
<ul className="news-section__list"> <ul className="news-section__list">
{[0, 1, 2, 3].map((card) => ( {data.map((card) => (
<li key={card} className="news-card"> <li key={card.id} className="news-card">
<Image src={image} alt="Card Image" /> <NewsCard
id={card.id}
<div className="news-card__text"> title={card.title}
<h5 className="news-card__date">23/09/2023</h5> image={card.image}
<h4> description={card.description}
Short title of the card describing the main content date={card.created_at}
</h4> />
<p>
Short paragraph description of the article, outlining
main story and focus.
</p>
</div>
<button className="news-card__more-btn">Подробнее</button>
</li> </li>
))} ))}
</ul> </ul>

View File

@ -0,0 +1,40 @@
import { baseAPI } from "@/Shared/API/baseAPI";
import { IFetch, IList } from "@/Shared/types";
import axios from "axios";
import { StaticImageData } from "next/image";
import { create } from "zustand";
interface INews {
results: IResult[];
}
interface IResult {
id: number;
image: StaticImageData;
title: string;
description: string;
created_at: string;
}
interface INewsHomeStore extends IFetch {
data: IResult[];
getNews: () => Promise<void>;
}
export const useNewsHome = create<INewsHomeStore>((set) => ({
data: [],
loading: false,
error: "",
getNews: async () => {
try {
set({ loading: true });
const response = await axios.get<INews>(`${baseAPI}/news/`);
set({ data: response.data.results.slice(0, 4) });
} catch (error: any) {
set({ error: error.message });
} finally {
set({ loading: false });
}
},
}));