diff --git a/app/news/[id]/page.tsx b/app/news/[id]/page.tsx new file mode 100644 index 0000000..35001fc --- /dev/null +++ b/app/news/[id]/page.tsx @@ -0,0 +1,3 @@ +import NewsDetailsPage from "@/Pages/NewsDetailsPage/NewsDetailsPage"; + +export default NewsDetailsPage; diff --git a/next.config.mjs b/next.config.mjs index 4678774..e8b1ccf 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,13 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "**", + }, + ], + }, +}; export default nextConfig; diff --git a/src/Entities/NewsCard/NewsCard.scss b/src/Entities/NewsCard/NewsCard.scss new file mode 100644 index 0000000..8add2b1 --- /dev/null +++ b/src/Entities/NewsCard/NewsCard.scss @@ -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; + } +} diff --git a/src/Entities/NewsCard/NewsCard.tsx b/src/Entities/NewsCard/NewsCard.tsx new file mode 100644 index 0000000..a8341a0 --- /dev/null +++ b/src/Entities/NewsCard/NewsCard.tsx @@ -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 = ({ + 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 ( +
+ Card Image + +
+
{sliceDate(date)}
+

{title}

+

{sliceDescription(description)}

+
+ + + Подробнее + +
+ ); +}; + +export default NewsCard; diff --git a/src/Features/SearchBar/SearchBar.tsx b/src/Features/SearchBar/SearchBar.tsx index 7eeca92..70fe3ad 100644 --- a/src/Features/SearchBar/SearchBar.tsx +++ b/src/Features/SearchBar/SearchBar.tsx @@ -4,13 +4,15 @@ import search_icon from "./icons/search-icon.svg"; interface ISearchBar { placeholder?: string; + style?: object; } const SearchBar: React.FC = ({ placeholder, + style, }: ISearchBar) => { return ( -
+
Search Icon diff --git a/src/Pages/AboutUsPage/AboutUsPage.scss b/src/Pages/AboutUsPage/AboutUsPage.scss index e69de29..ad5ff99 100644 --- a/src/Pages/AboutUsPage/AboutUsPage.scss +++ b/src/Pages/AboutUsPage/AboutUsPage.scss @@ -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; + } + } + } + } +} diff --git a/src/Pages/AboutUsPage/AboutUsPage.tsx b/src/Pages/AboutUsPage/AboutUsPage.tsx index 64e7abb..9662a87 100644 --- a/src/Pages/AboutUsPage/AboutUsPage.tsx +++ b/src/Pages/AboutUsPage/AboutUsPage.tsx @@ -1,7 +1,66 @@ +import HeaderText from "@/Shared/UI/HeaderText/HeaderText"; import "./AboutUsPage.scss"; +import Image from "next/image"; +import image from "./assets/image.png"; const AboutUsPage = () => { - return
AboutUsPage
; + return ( +
+ О нас +
+ About Us Image +
+ +
+

Don’t wait. The purpose of our lives is to be happy!

+
+

+ 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. +

+

+ When you are ready to indulge your sense of excitement, + check out the range of water- sports opportunities at the + resort’s 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.{" "} +

+

+ 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. +

+

+ 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. +

+
+
+
+ ); }; export default AboutUsPage; diff --git a/src/Pages/AboutUsPage/assets/image.png b/src/Pages/AboutUsPage/assets/image.png new file mode 100644 index 0000000..51a9e8f Binary files /dev/null and b/src/Pages/AboutUsPage/assets/image.png differ diff --git a/src/Pages/NewsDetailsPage/NewsDetailsPage.scss b/src/Pages/NewsDetailsPage/NewsDetailsPage.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/Pages/NewsDetailsPage/NewsDetailsPage.tsx b/src/Pages/NewsDetailsPage/NewsDetailsPage.tsx new file mode 100644 index 0000000..f79d546 --- /dev/null +++ b/src/Pages/NewsDetailsPage/NewsDetailsPage.tsx @@ -0,0 +1,7 @@ +import "./NewsDetailsPage.scss"; + +const NewsDetailsPage = () => { + return
NewsDetailsPage
; +}; + +export default NewsDetailsPage; diff --git a/src/Pages/NewsPage/NewsPage.scss b/src/Pages/NewsPage/NewsPage.scss index e69de29..7416004 100644 --- a/src/Pages/NewsPage/NewsPage.scss +++ b/src/Pages/NewsPage/NewsPage.scss @@ -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; + } +} diff --git a/src/Pages/NewsPage/NewsPage.tsx b/src/Pages/NewsPage/NewsPage.tsx index 224df72..a6df3cc 100644 --- a/src/Pages/NewsPage/NewsPage.tsx +++ b/src/Pages/NewsPage/NewsPage.tsx @@ -1,7 +1,14 @@ +import HeaderText from "@/Shared/UI/HeaderText/HeaderText"; import "./NewsPage.scss"; +import NewsList from "@/Widgets/NewsList/NewsList"; const NewsPage = () => { - return
NewsPage
; + return ( +
+ Новости + +
+ ); }; export default NewsPage; diff --git a/src/Pages/StatisticsPage/StatisticsPage.scss b/src/Pages/StatisticsPage/StatisticsPage.scss index e69de29..465bc95 100644 --- a/src/Pages/StatisticsPage/StatisticsPage.scss +++ b/src/Pages/StatisticsPage/StatisticsPage.scss @@ -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; + } +} diff --git a/src/Pages/StatisticsPage/StatisticsPage.tsx b/src/Pages/StatisticsPage/StatisticsPage.tsx index 9ebcd69..1f8c9cd 100644 --- a/src/Pages/StatisticsPage/StatisticsPage.tsx +++ b/src/Pages/StatisticsPage/StatisticsPage.tsx @@ -1,7 +1,19 @@ +import HeaderText from "@/Shared/UI/HeaderText/HeaderText"; import "./StatisticsPage.scss"; +import SearchBar from "@/Features/SearchBar/SearchBar"; +import StatisticsTable from "@/Widgets/StatisticsTable/StatisticsTable"; const StatisticsPage = () => { - return
StatisticsPage
; + return ( +
+ Статистика + + +
+ ); }; export default StatisticsPage; diff --git a/src/Pages/VolunteersPage/VolunteersPage.scss b/src/Pages/VolunteersPage/VolunteersPage.scss index e69de29..6e8aa98 100644 --- a/src/Pages/VolunteersPage/VolunteersPage.scss +++ b/src/Pages/VolunteersPage/VolunteersPage.scss @@ -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; + } +} diff --git a/src/Pages/VolunteersPage/VolunteersPage.tsx b/src/Pages/VolunteersPage/VolunteersPage.tsx index 62368a4..d4bc50f 100644 --- a/src/Pages/VolunteersPage/VolunteersPage.tsx +++ b/src/Pages/VolunteersPage/VolunteersPage.tsx @@ -1,7 +1,14 @@ +import HeaderText from "@/Shared/UI/HeaderText/HeaderText"; import "./VolunteersPage.scss"; +import VolunteersTable from "@/Widgets/VolunteersTable/VolunteersTable"; const VolunteersPage = () => { - return
VolunteersPage
; + return ( +
+ Волонтеры + +
+ ); }; export default VolunteersPage; diff --git a/src/Shared/UI/HeaderText/HeaderText.scss b/src/Shared/UI/HeaderText/HeaderText.scss new file mode 100644 index 0000000..0f89123 --- /dev/null +++ b/src/Shared/UI/HeaderText/HeaderText.scss @@ -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; + } +} diff --git a/src/Shared/UI/HeaderText/HeaderText.tsx b/src/Shared/UI/HeaderText/HeaderText.tsx new file mode 100644 index 0000000..8b200f5 --- /dev/null +++ b/src/Shared/UI/HeaderText/HeaderText.tsx @@ -0,0 +1,13 @@ +import "./HeaderText.scss"; + +interface IHeaderText { + children?: React.ReactNode; +} + +const HeaderText: React.FC = ({ + children, +}: IHeaderText) => { + return

{children}

; +}; + +export default HeaderText; diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 91516ec..9b458aa 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -4,3 +4,9 @@ export interface IFetch { loading: boolean; error?: string; } + +export interface IList { + count: number; + next: number | null; + previous: number | null; +} diff --git a/src/Widgets/NewsList/NewsList.scss b/src/Widgets/NewsList/NewsList.scss new file mode 100644 index 0000000..7f79d6a --- /dev/null +++ b/src/Widgets/NewsList/NewsList.scss @@ -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; + } +} diff --git a/src/Widgets/NewsList/NewsList.tsx b/src/Widgets/NewsList/NewsList.tsx new file mode 100644 index 0000000..daff7c6 --- /dev/null +++ b/src/Widgets/NewsList/NewsList.tsx @@ -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 ( +
    + {data.results.map((card) => ( +
  • + +
  • + ))} +
+ ); +}; + +export default NewsList; diff --git a/src/Widgets/NewsList/news.store.ts b/src/Widgets/NewsList/news.store.ts new file mode 100644 index 0000000..28b75f9 --- /dev/null +++ b/src/Widgets/NewsList/news.store.ts @@ -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; +} + +export const useNews = create((set) => ({ + data: { + count: 0, + next: null, + previous: null, + results: [], + }, + loading: false, + error: "", + getNews: async () => { + try { + set({ loading: true }); + const response = await axios.get(`${baseAPI}/news/`); + + set({ data: response.data }); + } catch (error: any) { + set({ error: error.message }); + } finally { + set({ loading: false }); + } + }, +})); diff --git a/src/Widgets/StatisticsTable/StatisticsTable.scss b/src/Widgets/StatisticsTable/StatisticsTable.scss new file mode 100644 index 0000000..ae01338 --- /dev/null +++ b/src/Widgets/StatisticsTable/StatisticsTable.scss @@ -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; + } + } + } +} diff --git a/src/Widgets/StatisticsTable/StatisticsTable.tsx b/src/Widgets/StatisticsTable/StatisticsTable.tsx new file mode 100644 index 0000000..a6ca51f --- /dev/null +++ b/src/Widgets/StatisticsTable/StatisticsTable.tsx @@ -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 ( +
+ {locationMenu && ( +
    e.stopPropagation()} + className="statistics-table__location" + > +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ )} +
+ + + + + + + + + + + + + + + {data.map((statistic) => ( + + + + + + + + + + ))} + +
setLocationMenu((prev) => !prev)} + > +
+ Город + Chevron Icon +
+
+
+ Добавлено дорог +
+ Arrow Down Icon + Arrow Up Icon +
+
+
+
+ Локальных дефектов +
+ Arrow Down Icon + Arrow Up Icon +
+
+
+
+ Очагов аварийности +
+ Arrow Down Icon + Arrow Up Icon +
+
+
+
+ Локальных дефектов исправлено +
+ Arrow Down Icon + Arrow Up Icon +
+
+
+
+ В планах ремонта +
+ Arrow Down Icon + Arrow Up Icon +
+
+
+
+ Отремонтировано +
+ Arrow Down Icon + Arrow Up Icon +
+
+
{statistic.name}{statistic.broken_road_1}{statistic.local_defect_3}{statistic.hotbed_of_accidents_2}{statistic.local_defect_fixed_6}{statistic.repair_plans_4}{statistic.repaired_5}
+
+
+ ); +}; + +export default StatisticsTable; diff --git a/src/Widgets/StatisticsTable/icons/arrow-down-icon.svg b/src/Widgets/StatisticsTable/icons/arrow-down-icon.svg new file mode 100644 index 0000000..c551a14 --- /dev/null +++ b/src/Widgets/StatisticsTable/icons/arrow-down-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Widgets/StatisticsTable/icons/arrow-up-icon.svg b/src/Widgets/StatisticsTable/icons/arrow-up-icon.svg new file mode 100644 index 0000000..ffd6c64 --- /dev/null +++ b/src/Widgets/StatisticsTable/icons/arrow-up-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Widgets/StatisticsTable/icons/chevron-down.svg b/src/Widgets/StatisticsTable/icons/chevron-down.svg new file mode 100644 index 0000000..6cee82d --- /dev/null +++ b/src/Widgets/StatisticsTable/icons/chevron-down.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Widgets/StatisticsTable/statistics.store.ts b/src/Widgets/StatisticsTable/statistics.store.ts new file mode 100644 index 0000000..6c696c2 --- /dev/null +++ b/src/Widgets/StatisticsTable/statistics.store.ts @@ -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; + getStatisticsForState: () => Promise; + getStatisticsForVillage: () => Promise; +} + +export const useStatistics = create((set) => ({ + data: [], + loading: false, + error: "", + getStatisticsForCity: async () => { + try { + set({ loading: true }); + + const response = await axios.get( + `${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( + `${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( + `${baseAPI}/report/village/stats/` + ); + + set({ data: response.data }); + } catch (error: any) { + set({ error: error.message }); + } finally { + set({ loading: false }); + } + }, +})); diff --git a/src/Widgets/VolunteersTable/VolunteersTable.scss b/src/Widgets/VolunteersTable/VolunteersTable.scss new file mode 100644 index 0000000..83f78ee --- /dev/null +++ b/src/Widgets/VolunteersTable/VolunteersTable.scss @@ -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; + } + } + } + } + } +} diff --git a/src/Widgets/VolunteersTable/VolunteersTable.tsx b/src/Widgets/VolunteersTable/VolunteersTable.tsx new file mode 100644 index 0000000..bf9e700 --- /dev/null +++ b/src/Widgets/VolunteersTable/VolunteersTable.tsx @@ -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 ( +
+ + + + + + + + + + + + + + {data.map((volunteer) => ( + + + + + + + + + ))} + +
Активист +
+ Добавлено дорог + Arrow Down Icon + Arrow Up Icon +
+
+
+ Получено голосов{" "} + Arrow Down Icon + Arrow Up Icon +
+
+
+ Оставлено голосов{" "} + Arrow Down Icon + Arrow Up Icon +
+
+
+ Рейтинг{" "} + Arrow Down Icon + Arrow Up Icon +
+
1 + {volunteer.username} + {volunteer.report_count}{volunteer.likes_received_count}{volunteer.likes_given_count}{volunteer.average_rating}
+
+ ); +}; + +export default VolunteersTable; diff --git a/src/Widgets/VolunteersTable/icons/arrow-down-icon.svg b/src/Widgets/VolunteersTable/icons/arrow-down-icon.svg new file mode 100644 index 0000000..c551a14 --- /dev/null +++ b/src/Widgets/VolunteersTable/icons/arrow-down-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Widgets/VolunteersTable/icons/arrow-up-icon.svg b/src/Widgets/VolunteersTable/icons/arrow-up-icon.svg new file mode 100644 index 0000000..ffd6c64 --- /dev/null +++ b/src/Widgets/VolunteersTable/icons/arrow-up-icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Widgets/VolunteersTable/volunteer.store.ts b/src/Widgets/VolunteersTable/volunteer.store.ts new file mode 100644 index 0000000..d0c6808 --- /dev/null +++ b/src/Widgets/VolunteersTable/volunteer.store.ts @@ -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; +} + +export const useVolunteers = create((set) => ({ + data: [], + loading: false, + error: "", + getVolunteers: async () => { + try { + set({ loading: true }); + + const response = await axios.get( + `${baseAPI}/report/user_ratings/` + ); + + set({ data: response.data }); + } catch (error: any) { + set({ error: error.message }); + } finally { + set({ loading: false }); + } + }, +})); diff --git a/src/Widgets/home/NewsSection/NewsSection.scss b/src/Widgets/home/NewsSection/NewsSection.scss index 46ff8c9..ab930e6 100644 --- a/src/Widgets/home/NewsSection/NewsSection.scss +++ b/src/Widgets/home/NewsSection/NewsSection.scss @@ -14,52 +14,6 @@ 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 { width: fit-content; display: flex; diff --git a/src/Widgets/home/NewsSection/NewsSection.tsx b/src/Widgets/home/NewsSection/NewsSection.tsx index 382350f..c0ecff0 100644 --- a/src/Widgets/home/NewsSection/NewsSection.tsx +++ b/src/Widgets/home/NewsSection/NewsSection.tsx @@ -1,32 +1,36 @@ +"use client"; + import "./NewsSection.scss"; import Image from "next/image"; import SectionHeader from "@/Shared/UI/SectionHeader/SectionHeader"; import image from "./assets/image.jpg"; import arrow_icon from "./icons/arrow-right-icon.svg"; import Link from "next/link"; +import { useNewsHome } from "./news-home.store"; +import { useEffect } from "react"; +import NewsCard from "@/Entities/NewsCard/NewsCard"; const NewsSection = () => { + const { data, loading, error, getNews } = useNewsHome(); + + useEffect(() => { + getNews(); + }, []); + return (
Новости
    - {[0, 1, 2, 3].map((card) => ( -
  • - Card Image - -
    -
    23/09/2023
    -

    - Short title of the card describing the main content -

    -

    - Short paragraph description of the article, outlining - main story and focus. -

    -
    - - + {data.map((card) => ( +
  • +
  • ))}
diff --git a/src/Widgets/home/NewsSection/news-home.store.ts b/src/Widgets/home/NewsSection/news-home.store.ts new file mode 100644 index 0000000..46e788b --- /dev/null +++ b/src/Widgets/home/NewsSection/news-home.store.ts @@ -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; +} + +export const useNewsHome = create((set) => ({ + data: [], + loading: false, + error: "", + getNews: async () => { + try { + set({ loading: true }); + const response = await axios.get(`${baseAPI}/news/`); + + set({ data: response.data.results.slice(0, 4) }); + } catch (error: any) { + set({ error: error.message }); + } finally { + set({ loading: false }); + } + }, +}));