diff --git a/src/app/news/News.scss b/src/app/news/News.scss index ec20536..9d2ceba 100644 --- a/src/app/news/News.scss +++ b/src/app/news/News.scss @@ -6,38 +6,16 @@ h2 { width: fit-content; } - - &__list { - display: grid; - grid-template-columns: 1fr 1fr 1fr 1fr; - gap: 30px; - } -} - -@media screen and (max-width: 1024px) { - .news { - &__list { - grid-template-columns: 1fr 1fr 1fr; - } - } } @media screen and (max-width: 768px) { .news { gap: 30px; - - &__list { - grid-template-columns: 1fr 1fr; - } } } @media screen and (max-width: 550px) { .news { gap: 20px; - - &__list { - grid-template-columns: 1fr; - } } } diff --git a/src/app/news/page.tsx b/src/app/news/page.tsx index 3a0290f..36d7d34 100644 --- a/src/app/news/page.tsx +++ b/src/app/news/page.tsx @@ -2,41 +2,20 @@ import "./News.scss"; import Typography from "@/shared/ui/components/Typography/Typography"; import { apiInstance } from "@/shared/config/apiConfig"; import { INewsList } from "@/shared/types/news-type"; -import NewsCard from "@/entities/NewsCard/NewsCard"; +import NewsList from "@/widgets/NewsList/NewsList"; -const News = async () => { - const getNews = async () => { - try { - const response = await apiInstance.get("/news/"); - - return response.data; - } catch (error) { - console.log(error); - - return { - results: [], - }; - } +const News = ({ + searchParams, +}: { + searchParams: { + ["страница-новостей"]: string; }; - - const data = await getNews(); +}) => { return (
Новости - +
); }; diff --git a/src/app/page.tsx b/src/app/page.tsx index b50d346..14a03ee 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -3,13 +3,7 @@ import Header from "@/widgets/Header/Header"; import StatisticsSection from "@/widgets/StatisticsSection/StatisticsSection"; import RatingSection from "@/widgets/RatingSection/RatingSection"; import NewsSection from "@/widgets/NewsSection/NewsSection"; - -const DynamicMap = dynamic( - () => import("@/widgets/MapSection/MapSection"), - { - ssr: false, - } -); +import MapSection from "@/widgets/MapSection/MapSection"; const Home = async ({ searchParams, @@ -25,18 +19,8 @@ const Home = async ({
- - + {/* */} +
); diff --git a/src/app/profile/layout.tsx b/src/app/profile/layout.tsx index f0d7568..e155759 100644 --- a/src/app/profile/layout.tsx +++ b/src/app/profile/layout.tsx @@ -2,16 +2,43 @@ import Typography from "@/shared/ui/components/Typography/Typography"; import "./Profile.scss"; import ProfileNav from "@/widgets/ProfileNav/ProfileNav"; import AuthGuard from "./AuthGuard"; +import { AxiosError } from "axios"; +import { apiInstance } from "@/shared/config/apiConfig"; +import { getServerSession } from "next-auth"; +import { authConfig } from "@/shared/config/authConfig"; +import { IProfile } from "@/shared/types/profile-type"; -const Profile = ({ +const Profile = async ({ children, }: Readonly<{ children: React.ReactNode; }>) => { + const session = await getServerSession(authConfig); + + const getProfile = async () => { + const Authorization = `Bearer ${session?.access_token}`; + const config = { + headers: { + Authorization, + }, + }; + try { + const response = await apiInstance.get<{ + report_count: number; + }>("/users/profile/", config); + + return response.data; + } catch (error: unknown) { + if (error instanceof AxiosError) console.log(error.message); + } + }; + + const data = await getProfile(); + return (
Личный кабинет - + {children}
diff --git a/src/app/profile/my-reports/page.tsx b/src/app/profile/my-reports/page.tsx index 501fa0a..fa81167 100644 --- a/src/app/profile/my-reports/page.tsx +++ b/src/app/profile/my-reports/page.tsx @@ -1,32 +1,14 @@ -import { apiInstance } from "@/shared/config/apiConfig"; -import { authConfig } from "@/shared/config/authConfig"; -import { IMyReportsList } from "@/shared/types/my-reports"; import ProfileTable from "@/widgets/ProfileTable/ProfileTable"; -import { getServerSession } from "next-auth"; import React from "react"; -const MyReports = async () => { - const session = await getServerSession(authConfig); - - const getMyReports = async () => { - const Authorization = `Bearer ${session?.access_token}`; - const config = { - headers: { - Authorization, - }, - }; - const res = await apiInstance.get( - "/users/reports/", - config - ); - - return res.data; - }; - - const data: IMyReportsList = await getMyReports(); +const MyReports = async ({ + searchParams, +}: { + searchParams: { ["страница-обращений"]: string }; +}) => { return (
- +
); }; diff --git a/src/app/statistics/page.tsx b/src/app/statistics/page.tsx index 665625d..105cd74 100644 --- a/src/app/statistics/page.tsx +++ b/src/app/statistics/page.tsx @@ -5,28 +5,15 @@ import { IStatistics } from "@/shared/types/statistics-type"; import { AxiosError } from "axios"; import StatisticsTable from "@/widgets/StatisticsTable/StatisticsTable"; -const Statistics = async () => { - const getStatistics = async (): Promise => { - try { - const response = await apiInstance.get( - "/report/city/stats/" - ); - - return response.data; - } catch (error: unknown) { - if (error instanceof AxiosError) { - return error.message; - } else { - return "Произошла непредвиденная ошибк"; - } - } - }; - - const data = await getStatistics(); +const Statistics = ({ + searchParams, +}: { + searchParams: { ["поиск-населенного-пункта"]: string }; +}) => { return (
Статистика - +
); }; diff --git a/src/app/volunteers/page.tsx b/src/app/volunteers/page.tsx index fed12e3..4c653bd 100644 --- a/src/app/volunteers/page.tsx +++ b/src/app/volunteers/page.tsx @@ -5,30 +5,11 @@ import { AxiosError } from "axios"; import { IUserRatings } from "@/shared/types/user-rating-type"; import VolunteersTable from "@/widgets/VolunteersTable/VolunteersTable"; -const Volunteers = async () => { - const getVolunteers = async (): Promise< - IUserRatings[] | string - > => { - try { - const response = await apiInstance.get( - "/report/user_ratings/" - ); - - return response.data; - } catch (error: unknown) { - if (error instanceof AxiosError) { - return error.message; - } else { - return "Произошла непредвиденная ошибк"; - } - } - }; - - const data = await getVolunteers(); +const Volunteers = () => { return (
Волонтеры - +
); }; diff --git a/src/features/Pagination/Pagination.tsx b/src/features/Pagination/Pagination.tsx index 049b21b..3a38e08 100644 --- a/src/features/Pagination/Pagination.tsx +++ b/src/features/Pagination/Pagination.tsx @@ -11,6 +11,7 @@ interface IPaginationProps { next: string | null; count: number; current_count: number; + limit: number; } const Pagination: React.FC = ({ @@ -20,8 +21,9 @@ const Pagination: React.FC = ({ next, count, current_count, + limit, }: IPaginationProps) => { - const pages_count = count % 8; + const pages_count = Math.ceil(count / limit); const showPages = () => { const btns = []; diff --git a/src/shared/types/profile-type.ts b/src/shared/types/profile-type.ts index f4dbfdf..319fdbc 100644 --- a/src/shared/types/profile-type.ts +++ b/src/shared/types/profile-type.ts @@ -6,4 +6,5 @@ export interface IProfile { image: string; role: number; govern_status: null; + report_count: number; } diff --git a/src/widgets/MapSection/HomeMap/HomeMap.tsx b/src/widgets/MapSection/HomeMap/HomeMap.tsx index 72988d3..6499e58 100644 --- a/src/widgets/MapSection/HomeMap/HomeMap.tsx +++ b/src/widgets/MapSection/HomeMap/HomeMap.tsx @@ -16,6 +16,8 @@ import geo_pink_icon from "./icons/geo-pink.svg"; import geo_purple_icon from "./icons/geo-purple.svg"; import geo_red_icon from "./icons/geo-red.svg"; import geo_yellow_icon from "./icons/geo-yellow.svg"; +import geo_white_icon from "./icons/geo-white.svg"; + import { DivIcon, Icon, @@ -27,6 +29,7 @@ import Link from "next/link"; import { Fragment, useEffect, useState } from "react"; import L from "leaflet"; import { ILocation } from "@/shared/types/location-type"; +import { useMapStore } from "../mapSectionStore"; interface IData { id: number; @@ -40,14 +43,13 @@ interface ILatLng { } interface IHomeMapProps { - data: IData[]; - latLng: ILatLng; + reports: IData[]; } const HomeMap: React.FC = ({ - data, - latLng, + reports, }: IHomeMapProps) => { + const { display_location, latLng } = useMapStore(); const [position, setPosition] = useState({ lat: 42.8746, lng: 74.606, @@ -70,6 +72,11 @@ const HomeMap: React.FC = ({ 6: createCustomIcon(geo_green_icon), }; + const searchResultIcon = new Icon({ + iconUrl: geo_white_icon.src, + iconSize: [100, 100], + }); + const categoryToPolyline: Record = { 1: { color: "rgba(230, 68, 82, 0.8)" }, 2: { color: "rgba(198, 152, 224, 0.8)" }, @@ -88,10 +95,23 @@ const HomeMap: React.FC = ({ setPosition(latLng); }, [latLng]); - return null; - }; + const defPosition = { + lat: 42.8746, + lng: 74.606, + }; - const defPos = [42.8746, 74.606]; + if ( + latLng.lat === defPosition.lat && + latLng.lng === defPosition.lng + ) + return null; + + return ( + + {display_location} + + ); + }; return ( = ({ url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> - {data.map((report) => + {reports.map((report) => report.location.length === 2 ? ( = ({ ) : null )} - {data?.map((report) => + {reports.map((report) => report.location.map((marker) => ( + + + + + + + + + + + + + + + + diff --git a/src/widgets/MapSection/MapSearch/MapSearch.tsx b/src/widgets/MapSection/MapSearch/MapSearch.tsx index 266ed53..3d3a68d 100644 --- a/src/widgets/MapSection/MapSearch/MapSearch.tsx +++ b/src/widgets/MapSection/MapSearch/MapSearch.tsx @@ -3,35 +3,68 @@ import "./MapSearch.scss"; import Image from "next/image"; import search from "./icons/search.svg"; -import { IDisplayMap } from "@/shared/types/map-type"; +import { useMapStore } from "../mapSectionStore"; +import { useEffect, useState } from "react"; +import { useDebounce } from "use-debounce"; +import { useRouter } from "next/navigation"; interface IMapSearchProps extends React.InputHTMLAttributes { - options: IDisplayMap[]; - setMapSearch: (string: string) => void; - setLatLng: ({ lat, lng }: { lat: number; lng: number }) => void; + searchParams: { + ["тип-дороги"]: string; + ["поиск-на-карте"]: string; + ["поиск-рейтинг"]: string; + ["страница-рейтинга"]: string; + }; } const MapSearch: React.FC = ({ - name, - placeholder, - value, - onChange, - options, - setMapSearch, - setLatLng, + searchParams, }: IMapSearchProps) => { + const router = useRouter(); + const [searchMap, setSearchMap] = useState( + searchParams["поиск-на-карте"] || "" + ); + const [query] = useDebounce(searchMap, 500); + + const { + setLatLng, + searchData: options, + getLocations, + setDisplayLocation, + } = useMapStore(); + const handleSubmit = ( display_name: string, latLng: { lat: number; lng: number } ) => { - setMapSearch(display_name); + setSearchMap(display_name); + setDisplayLocation(display_name); setLatLng(latLng); + + window.scrollTo({ + top: 1400, + behavior: "smooth", + }); }; + + useEffect(() => { + getLocations(searchMap); + + router.push( + `?тип-дороги=${ + searchParams["тип-дороги"] || "1,2,3,4,5,6" + }&поиск-на-карте=${searchMap}&поиск-рейтинг=${ + searchParams["поиск-рейтинг"] || "" + }&страница-рейтинга=${searchParams["страница-рейтинга"] || ""}`, + { scroll: false } + ); + }, [query]); + return (
{ + onSubmit={(e: React.MouseEvent) => { e.preventDefault(); handleSubmit(options[0].display_name, { lat: +options[0].lat, @@ -42,10 +75,10 @@ const MapSearch: React.FC = ({
Search Icon setSearchMap(e.target.value)} + name="map-search" type="text" />
@@ -59,11 +92,16 @@ const MapSearch: React.FC = ({
  • ); }; diff --git a/src/widgets/MapSection/Switch/Switch.tsx b/src/widgets/MapSection/Switch/Switch.tsx index ccf839c..3718a15 100644 --- a/src/widgets/MapSection/Switch/Switch.tsx +++ b/src/widgets/MapSection/Switch/Switch.tsx @@ -5,7 +5,7 @@ import "./Switch.scss"; import Link from "next/link"; interface ISwitchProps { - defaultState?: boolean; + defaultState: boolean; color?: string; href: string; } @@ -25,7 +25,7 @@ const Switch: React.FC = ({ onClick={() => setToggleSwitch((prev) => !prev)} style={{ backgroundColor: !toggleSwitch - ? "rgb(50, 48, 58)" + ? "rgb(71, 85, 105)" : color ? color : "rgb(230, 68, 82)", diff --git a/src/widgets/MapSection/mapSectionStore.ts b/src/widgets/MapSection/mapSectionStore.ts index 18d12eb..fb0dfac 100644 --- a/src/widgets/MapSection/mapSectionStore.ts +++ b/src/widgets/MapSection/mapSectionStore.ts @@ -1,46 +1,22 @@ -import { apiInstance } from "@/shared/config/apiConfig"; -import { IFetch } from "@/shared/types/fetch-type"; -import { IList } from "@/shared/types/list-type"; import { IDisplayMap } from "@/shared/types/map-type"; -import { IReport } from "@/shared/types/report-type"; import axios from "axios"; import { create } from "zustand"; -interface IFetchReports extends IList { - results: IReport[]; -} - -interface IMapStore extends IFetch { - data: IFetchReports; - searchData: IDisplayMap[]; - getReports: (categories: string) => Promise; +interface IMapStore { + setLatLng: (latLng: { lat: number; lng: number }) => void; + latLng: { lat: number; lng: number }; getLocations: (query: string) => void; + searchData: IDisplayMap[]; + setDisplayLocation: (display_location: string) => void; + display_location: string; } export const useMapStore = create((set) => ({ - data: { - count: 0, - previous: null, - next: null, - results: [], - }, searchData: [], - isLoading: false, - error: "", - getReports: async (categories: string = "1,2,3,4,5,6") => { - try { - set({ isLoading: true }); - - const response = await apiInstance.get( - `/report/?category=${categories}` - ); - - set({ data: response.data }); - } catch (error: any) { - set({ error: error.message }); - } finally { - set({ isLoading: false }); - } + display_location: "", + latLng: { + lat: 42.8746, + lng: 74.606, }, getLocations: async (query: string = "") => { const params: Record = { @@ -60,4 +36,10 @@ export const useMapStore = create((set) => ({ set({ searchData: inKG }); }, + setLatLng: (latLng: { lat: number; lng: number }) => { + set({ latLng: latLng }); + }, + setDisplayLocation: (display_location: string) => { + set({ display_location: display_location }); + }, })); diff --git a/src/widgets/NewsList/NewsList.scss b/src/widgets/NewsList/NewsList.scss new file mode 100644 index 0000000..7b87c80 --- /dev/null +++ b/src/widgets/NewsList/NewsList.scss @@ -0,0 +1,38 @@ +.news-list { + display: flex; + flex-direction: column; + gap: 20px; + ul { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 30px; + } +} + +@media screen and (max-width: 1024px) { + .news-list { + ul { + grid-template-columns: 1fr 1fr 1fr; + } + } +} + +@media screen and (max-width: 768px) { + .news-list { + gap: 30px; + + ul { + grid-template-columns: 1fr 1fr; + } + } +} + +@media screen and (max-width: 550px) { + .news-list { + gap: 20px; + + ul { + grid-template-columns: 1fr; + } + } +} diff --git a/src/widgets/NewsList/NewsList.tsx b/src/widgets/NewsList/NewsList.tsx new file mode 100644 index 0000000..0181634 --- /dev/null +++ b/src/widgets/NewsList/NewsList.tsx @@ -0,0 +1,54 @@ +"use client"; + +import "./NewsList.scss"; +import NewsCard from "@/entities/NewsCard/NewsCard"; +import { useNewsStore } from "./newsStore"; +import { useEffect, useState } from "react"; +import Pagination from "@/features/Pagination/Pagination"; + +interface INewsListProps { + searchParams: { + ["страница-новостей"]: string; + }; +} + +const NewsList: React.FC = ({ + searchParams, +}: INewsListProps) => { + const [activePage, setActivePage] = useState( + +searchParams["страница-новостей"] || 1 + ); + const { data: news, getNews, isLoading, error } = useNewsStore(); + + useEffect(() => { + getNews(activePage); + }, []); + return ( +
    +
      + {news.results.map((news) => ( +
    • + +
    • + ))} +
    + +
    + ); +}; + +export default NewsList; diff --git a/src/widgets/NewsList/newsStore.ts b/src/widgets/NewsList/newsStore.ts new file mode 100644 index 0000000..063fd0b --- /dev/null +++ b/src/widgets/NewsList/newsStore.ts @@ -0,0 +1,38 @@ +import { apiInstance } from "@/shared/config/apiConfig"; +import { IFetch } from "@/shared/types/fetch-type"; +import { INewsList } from "@/shared/types/news-type"; +import { AxiosError } from "axios"; +import { create } from "zustand"; + +interface useNewsStore extends IFetch { + data: INewsList; + getNews: (page: number) => void; +} + +export const useNewsStore = create((set) => ({ + data: { + count: 0, + previous: null, + next: null, + results: [], + }, + error: "", + isLoading: false, + getNews: async (page: number) => { + try { + set({ isLoading: true }); + + const res = await apiInstance.get(`/news/?page${page}`); + + set({ data: res.data }); + } catch (error: unknown) { + if (error instanceof AxiosError) { + set({ error: error.message }); + } else { + set({ error: "An error ocured" }); + } + } finally { + set({ isLoading: false }); + } + }, +})); diff --git a/src/widgets/NewsSection/newsSectionStore.ts b/src/widgets/NewsSection/newsSectionStore.ts index 1dace1b..1f1d34d 100644 --- a/src/widgets/NewsSection/newsSectionStore.ts +++ b/src/widgets/NewsSection/newsSectionStore.ts @@ -1,7 +1,7 @@ import { apiInstance } from "@/shared/config/apiConfig"; import { IList } from "@/shared/types/list-type"; import { INews } from "@/shared/types/news-type"; -import axios, { AxiosError } from "axios"; +import { AxiosError } from "axios"; interface IFetchNews extends IList { results: INews[]; diff --git a/src/widgets/ProfileNav/ProfileNav.tsx b/src/widgets/ProfileNav/ProfileNav.tsx index ffb5c15..aa6c537 100644 --- a/src/widgets/ProfileNav/ProfileNav.tsx +++ b/src/widgets/ProfileNav/ProfileNav.tsx @@ -5,7 +5,13 @@ import LogoutButton from "@/features/LogoutButton/LogoutButton"; import Link from "next/link"; import { usePathname } from "next/navigation"; -const ProfileNav = () => { +interface IProfileNavProps { + report_count: number; +} + +const ProfileNav: React.FC = ({ + report_count, +}: IProfileNavProps) => { const pathname = usePathname(); return (