diff --git a/src/app/page.tsx b/src/app/page.tsx index b9837b3..b50d346 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -18,6 +18,7 @@ const Home = async ({ ["тип-дороги"]: string; ["поиск-на-карте"]: string; ["поиск-рейтинг"]: string; + ["страница-рейтинга"]: string; }; }) => { return ( @@ -28,11 +29,13 @@ const Home = async ({ categories={searchParams["тип-дороги"]} queryMap={searchParams["поиск-на-карте"]} queryRating={searchParams["поиск-рейтинг"]} + page={searchParams["страница-рейтинга"]} /> diff --git a/src/entities/Pagination/Pagination.scss b/src/entities/Pagination/Pagination.scss new file mode 100644 index 0000000..92f8b98 --- /dev/null +++ b/src/entities/Pagination/Pagination.scss @@ -0,0 +1,40 @@ +.pagination { + margin-top: 40px; + display: flex; + + button { + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid rgb(208, 213, 221); + background-color: rgb(255, 255, 255); + } + + &__prev { + border-radius: 8px 0px 0px 8px; + } + + &__next { + border-radius: 0px 8px 8px 0px; + } + + &__page, + &__page_active { + font-size: 14px; + font-weight: 500; + line-height: 20px; + color: rgb(52, 64, 84); + } + + &__page_active { + background-color: rgb(249, 250, 251) !important; + } + + &__page:hover, + &__prev:hover, + &__next:hover { + background-color: rgb(249, 250, 251); + } +} diff --git a/src/entities/Pagination/Pagination.tsx b/src/entities/Pagination/Pagination.tsx new file mode 100644 index 0000000..31dea51 --- /dev/null +++ b/src/entities/Pagination/Pagination.tsx @@ -0,0 +1,140 @@ +import "./Pagination.scss"; +import arrow_right from "./icons/arrow-right.svg"; +import arrow_left from "./icons/arrow-left.svg"; +import Image from "next/image"; +import { useState } from "react"; + +interface IPaginationProps { + setActivePage: (page: number | ((prev: number) => number)) => void; + activePage: number; + prev: string | null; + next: string | null; + count: number; + current_count: number; +} + +const Pagination: React.FC = ({ + setActivePage, + activePage, + prev, + next, + count, + current_count, +}: IPaginationProps) => { + const pages_count = count % 8; + const showPages = () => { + const btns = []; + + for (let i = 1; i <= pages_count; i++) { + btns.push( + + ); + } + + if (current_count < 8 && activePage === 1) { + return [...btns.slice(0, 1)]; + } + + if (pages_count > 5) { + if (activePage > 2) { + if ( + activePage + 2 === pages_count - 1 || + activePage + 1 === pages_count - 1 || + activePage === pages_count - 1 || + activePage === pages_count + ) { + const newBtns = [ + ...btns.slice(0, 1), + , + ...btns.slice( + activePage - + (activePage + 2 === pages_count - 1 ? 2 : 1) + ), + ]; + + if (newBtns.length < 6) { + return [ + ...btns.slice(0, 1), + , + ...btns.slice(-4), + ]; + } + + return newBtns; + } else { + return [ + ...btns.slice(0, 1), + , + + ...btns.slice( + activePage - (activePage === 3 ? 1 : 2), + activePage + 1 + ), + + , + ...btns.slice(-2), + ]; + } + } else { + return [ + ...btns.slice(0, 3), + , + ...btns.slice(-2), + ]; + } + } + + return btns; + }; + + const prevPage = () => { + if (activePage - 1 === 0) return; + setActivePage((prev) => prev - 1); + }; + + const nextPage = () => { + if (activePage + 1 > pages_count) return; + setActivePage((prev) => prev + 1); + }; + return ( +
+ + {showPages().map((btn) => btn)} + +
+ ); +}; + +export default Pagination; diff --git a/src/entities/Pagination/icons/arrow-left.svg b/src/entities/Pagination/icons/arrow-left.svg new file mode 100644 index 0000000..61eb8af --- /dev/null +++ b/src/entities/Pagination/icons/arrow-left.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/src/entities/Pagination/icons/arrow-right.svg b/src/entities/Pagination/icons/arrow-right.svg new file mode 100644 index 0000000..45b4244 --- /dev/null +++ b/src/entities/Pagination/icons/arrow-right.svg @@ -0,0 +1,7 @@ + + + Created with Pixso. + + + + diff --git a/src/shared/types/location-type.ts b/src/shared/types/location-type.ts index 0522ff3..cb12e66 100644 --- a/src/shared/types/location-type.ts +++ b/src/shared/types/location-type.ts @@ -1,6 +1,6 @@ export interface ILocation { id: 4; - latitude: number; - longitude: number; + latitude: string; + longitude: string; address: string; } diff --git a/src/widgets/MapSection/HomeMap/HomeMap.tsx b/src/widgets/MapSection/HomeMap/HomeMap.tsx index 791632b..72988d3 100644 --- a/src/widgets/MapSection/HomeMap/HomeMap.tsx +++ b/src/widgets/MapSection/HomeMap/HomeMap.tsx @@ -5,6 +5,7 @@ import "leaflet/dist/leaflet.css"; import { MapContainer, Marker, + Polyline, Popup, TileLayer, useMap, @@ -15,10 +16,15 @@ 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 { DivIcon, Icon, LatLngTuple } from "leaflet"; +import { + DivIcon, + Icon, + LatLngExpression, + LatLngTuple, +} from "leaflet"; import { StaticImageData } from "next/image"; import Link from "next/link"; -import { useEffect, useState } from "react"; +import { Fragment, useEffect, useState } from "react"; import L from "leaflet"; import { ILocation } from "@/shared/types/location-type"; @@ -34,7 +40,7 @@ interface ILatLng { } interface IHomeMapProps { - data: IData[] | undefined; + data: IData[]; latLng: ILatLng; } @@ -60,8 +66,17 @@ const HomeMap: React.FC = ({ 2: createCustomIcon(geo_pink_icon), 3: createCustomIcon(geo_purple_icon), 4: createCustomIcon(geo_orange_icon), - 5: createCustomIcon(geo_green_icon), - 6: createCustomIcon(geo_yellow_icon), + 5: createCustomIcon(geo_yellow_icon), + 6: createCustomIcon(geo_green_icon), + }; + + const categoryToPolyline: Record = { + 1: { color: "rgba(230, 68, 82, 0.8)" }, + 2: { color: "rgba(198, 152, 224, 0.8)" }, + 3: { color: "rgba(135, 40, 157, 0.8)" }, + 4: { color: "rgba(247, 181, 84, 0.8)" }, + 5: { color: "rgba(254, 211, 99, 0.8)" }, + 6: { color: "rgba(158, 221, 128, 0.8)" }, }; const LocationMark = () => { @@ -76,6 +91,8 @@ const HomeMap: React.FC = ({ return null; }; + const defPos = [42.8746, 74.606]; + return ( = ({ attribution='© OpenStreetMap contributors' url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> - {data?.map((marker) => ( - - - - {marker.location[0].address} - - - - ))} + + {data.map((report) => + report.location.length === 2 ? ( + + ) : null + )} + + {data?.map((report) => + report.location.map((marker) => ( + + + + {marker.address} + + + + )) + )} ); diff --git a/src/widgets/MapSection/MapSection.tsx b/src/widgets/MapSection/MapSection.tsx index f43ccbf..ad667c4 100644 --- a/src/widgets/MapSection/MapSection.tsx +++ b/src/widgets/MapSection/MapSection.tsx @@ -29,6 +29,7 @@ const MapSection: React.FC = ({ categories = "1,2,3,4,5,6", queryMap, queryRating, + page = "1", }: IMapSectionProps) => { const [mapSearch, setMapSearch] = useState(queryMap || ""); const [latLng, setLatLng] = useState({ @@ -57,7 +58,9 @@ const MapSection: React.FC = ({ router.push( `/?тип-дороги=${categories}${ mapSearch ? `&поиск-на-карте=${mapSearch}` : "" - }${queryRating ? `&поиск-рейтинг=${queryRating}` : ""}`, + }${ + queryRating ? `&поиск-рейтинг=${queryRating}` : "" + }&страница-рейтинга=${page}`, { scroll: false, } @@ -136,7 +139,7 @@ const MapSection: React.FC = ({ - + ); }; diff --git a/src/widgets/RatingSection/RatingSection.scss b/src/widgets/RatingSection/RatingSection.scss index 75f7158..04007b1 100644 --- a/src/widgets/RatingSection/RatingSection.scss +++ b/src/widgets/RatingSection/RatingSection.scss @@ -35,7 +35,7 @@ height: 100%; display: grid; align-items: center; - grid-template-columns: 120px 200px 210px 380px 165px 92px; + grid-template-columns: 120px 200px 230px 380px 165px 92px; td { button { @@ -63,7 +63,7 @@ height: 90px; padding: 10px 0; display: grid; - grid-template-columns: 120px 200px 210px 380px 165px 92px; + grid-template-columns: 120px 200px 230px 380px 165px 92px; align-items: center; td { diff --git a/src/widgets/RatingSection/RatingSection.tsx b/src/widgets/RatingSection/RatingSection.tsx index 460f977..836ca16 100644 --- a/src/widgets/RatingSection/RatingSection.tsx +++ b/src/widgets/RatingSection/RatingSection.tsx @@ -19,6 +19,7 @@ import RoadType from "@/entities/RoadType/RoadType"; import Typography from "@/shared/ui/components/Typography/Typography"; import Paragraph from "@/shared/ui/components/Paragraph/Paragraph"; import arrows from "../../shared/icons/arrows.svg"; +import Pagination from "@/entities/Pagination/Pagination"; interface IRatingSectionProps { [key: string]: string; @@ -28,10 +29,12 @@ const RatingSection: React.FC = ({ categories = "1,2,3,4,5,6", queryMap, queryRating, + page = "1", }: IRatingSectionProps) => { const [ratingSearch, setRatingSearch] = useState( queryRating || "" ); + const [activePage, setActivePage] = useState(+page); const router = useRouter(); const reports = useRatingStore(useShallow((state) => state.data)); @@ -40,7 +43,7 @@ const RatingSection: React.FC = ({ ); useEffect(() => { - getReports(ratingSearch); + getReports(ratingSearch, +page); }, []); const handleSubmit: React.FormEventHandler< @@ -53,15 +56,36 @@ const RatingSection: React.FC = ({ router.push( `/?тип-дороги=${categories}${ queryMap ? `&поиск-на-карте=${queryMap}` : "" - }${ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : ""}`, + }${ + ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : "" + }&страница-рейтинга=${page}`, { scroll: false, } ); - getReports(ratingSearch); + getReports(ratingSearch, activePage); + + if (reports.results.length < 8 && page !== "1") { + setActivePage(1); + } }; + useEffect(() => { + router.push( + `/?тип-дороги=${categories}${ + queryMap ? `&поиск-на-карте=${queryMap}` : "" + }${ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : ""}${ + activePage === 1 ? "" : `&страница-рейтинг=${activePage}` + }`, + { + scroll: false, + } + ); + + getReports(ratingSearch, activePage); + }, [activePage]); + const sliceDate = (date: string) => { return `${date.slice(8, 10)}.${date.slice(5, 7)}.${date.slice( 0, @@ -177,6 +201,15 @@ const RatingSection: React.FC = ({ + + ); }; diff --git a/src/widgets/RatingSection/ratingSectionStore.ts b/src/widgets/RatingSection/ratingSectionStore.ts index bad7864..8cc4619 100644 --- a/src/widgets/RatingSection/ratingSectionStore.ts +++ b/src/widgets/RatingSection/ratingSectionStore.ts @@ -11,7 +11,7 @@ interface IFetchReports extends IList { interface IRatingStore extends IFetch { data: IFetchReports; - getReports: (categories: string) => Promise; + getReports: (categories: string, page: number) => Promise; } export const useRatingStore = create((set) => ({ @@ -23,12 +23,13 @@ export const useRatingStore = create((set) => ({ }, isLoading: false, error: "", - getReports: async (query: string = "") => { + getReports: async (query: string = "", page: number = 1) => { try { set({ isLoading: true }); - const data = (await apiInstance.get("/report/")) - .data; + const data = ( + await apiInstance.get(`/report/?page=${page}`) + ).data; const searched = data.results.filter((rating) => { return rating.location.some((location) => {