made pagination and fixed map

This commit is contained in:
Alibek 2024-02-15 22:45:27 +06:00
parent ed72067108
commit a12aaa1213
11 changed files with 306 additions and 36 deletions

View File

@ -18,6 +18,7 @@ const Home = async ({
["тип-дороги"]: string; ["тип-дороги"]: string;
["поиск-на-карте"]: string; ["поиск-на-карте"]: string;
["поиск-рейтинг"]: string; ["поиск-рейтинг"]: string;
["страница-рейтинга"]: string;
}; };
}) => { }) => {
return ( return (
@ -28,11 +29,13 @@ const Home = async ({
categories={searchParams["тип-дороги"]} categories={searchParams["тип-дороги"]}
queryMap={searchParams["поиск-на-карте"]} queryMap={searchParams["поиск-на-карте"]}
queryRating={searchParams["поиск-рейтинг"]} queryRating={searchParams["поиск-рейтинг"]}
page={searchParams["страница-рейтинга"]}
/> />
<RatingSection <RatingSection
categories={searchParams["тип-дороги"]} categories={searchParams["тип-дороги"]}
queryMap={searchParams["поиск-на-карте"]} queryMap={searchParams["поиск-на-карте"]}
queryRating={searchParams["поиск-рейтинг"]} queryRating={searchParams["поиск-рейтинг"]}
page={searchParams["страница-рейтинга"]}
/> />
<NewsSection /> <NewsSection />
</div> </div>

View File

@ -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);
}
}

View File

@ -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<IPaginationProps> = ({
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(
<button
onClick={() => setActivePage(i)}
key={i}
className={
activePage === i
? "pagination__page_active"
: "pagination__page"
}
>
{i}
</button>
);
}
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),
<button key="..." disabled>
...
</button>,
...btns.slice(
activePage -
(activePage + 2 === pages_count - 1 ? 2 : 1)
),
];
if (newBtns.length < 6) {
return [
...btns.slice(0, 1),
<button key="..." disabled>
...
</button>,
...btns.slice(-4),
];
}
return newBtns;
} else {
return [
...btns.slice(0, 1),
<button key="...1" disabled>
...
</button>,
...btns.slice(
activePage - (activePage === 3 ? 1 : 2),
activePage + 1
),
<button key="..." disabled>
...
</button>,
...btns.slice(-2),
];
}
} else {
return [
...btns.slice(0, 3),
<button key="..." disabled>
...
</button>,
...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 (
<div className="pagination">
<button
onClick={prevPage}
disabled={prev === null}
className="pagination__prev"
>
<Image src={arrow_left} alt="" />
</button>
{showPages().map((btn) => btn)}
<button
onClick={nextPage}
disabled={next === null}
className="pagination__next"
>
<Image src={arrow_right} alt="" />
</button>
</div>
);
};
export default Pagination;

View File

@ -0,0 +1,7 @@
<svg width="13.336426" height="13.343750" viewBox="0 0 13.3364 13.3438" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs/>
<path id="Icon" d="M12.5015 6.67188L0.834961 6.67188M6.66846 12.5059L0.834961 6.67188L6.66846 0.838867" stroke="#344054" stroke-opacity="1.000000" stroke-width="1.670000" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View File

@ -0,0 +1,7 @@
<svg width="13.336426" height="13.343750" viewBox="0 0 13.3364 13.3438" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<desc>
Created with Pixso.
</desc>
<defs/>
<path id="Icon" d="M0.834961 6.67188L12.5015 6.67188M6.66846 0.838867L12.5015 6.67188L6.66846 12.5059" stroke="#344054" stroke-opacity="1.000000" stroke-width="1.670000" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 416 B

View File

@ -1,6 +1,6 @@
export interface ILocation { export interface ILocation {
id: 4; id: 4;
latitude: number; latitude: string;
longitude: number; longitude: string;
address: string; address: string;
} }

View File

@ -5,6 +5,7 @@ import "leaflet/dist/leaflet.css";
import { import {
MapContainer, MapContainer,
Marker, Marker,
Polyline,
Popup, Popup,
TileLayer, TileLayer,
useMap, 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_purple_icon from "./icons/geo-purple.svg";
import geo_red_icon from "./icons/geo-red.svg"; import geo_red_icon from "./icons/geo-red.svg";
import geo_yellow_icon from "./icons/geo-yellow.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 { StaticImageData } from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useEffect, useState } from "react"; import { Fragment, useEffect, useState } from "react";
import L from "leaflet"; import L from "leaflet";
import { ILocation } from "@/shared/types/location-type"; import { ILocation } from "@/shared/types/location-type";
@ -34,7 +40,7 @@ interface ILatLng {
} }
interface IHomeMapProps { interface IHomeMapProps {
data: IData[] | undefined; data: IData[];
latLng: ILatLng; latLng: ILatLng;
} }
@ -60,8 +66,17 @@ const HomeMap: React.FC<IHomeMapProps> = ({
2: createCustomIcon(geo_pink_icon), 2: createCustomIcon(geo_pink_icon),
3: createCustomIcon(geo_purple_icon), 3: createCustomIcon(geo_purple_icon),
4: createCustomIcon(geo_orange_icon), 4: createCustomIcon(geo_orange_icon),
5: createCustomIcon(geo_green_icon), 5: createCustomIcon(geo_yellow_icon),
6: createCustomIcon(geo_yellow_icon), 6: createCustomIcon(geo_green_icon),
};
const categoryToPolyline: Record<number, { color: string }> = {
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 = () => { const LocationMark = () => {
@ -76,6 +91,8 @@ const HomeMap: React.FC<IHomeMapProps> = ({
return null; return null;
}; };
const defPos = [42.8746, 74.606];
return ( return (
<MapContainer <MapContainer
center={position} center={position}
@ -87,24 +104,43 @@ const HomeMap: React.FC<IHomeMapProps> = ({
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/> />
{data?.map((marker) => (
<Marker {data.map((report) =>
key={marker.id} report.location.length === 2 ? (
icon={icons[marker.category]} <Polyline
position={ key={report.id}
[ pathOptions={categoryToPolyline[report.category]}
+marker.location[0].latitude, positions={[
+marker.location[0].longitude, [
] as LatLngTuple parseFloat(report.location[0].latitude),
} parseFloat(report.location[0].longitude),
> ],
<Popup> [
<Link href={`/report/${marker.location[0].id}`}> parseFloat(report.location[1].latitude),
{marker.location[0].address} parseFloat(report.location[1].longitude),
</Link> ],
</Popup> ]}
</Marker> />
))} ) : null
)}
{data?.map((report) =>
report.location.map((marker) => (
<Marker
key={marker.id}
icon={icons[report.category]}
position={
[+marker.latitude, +marker.longitude] as LatLngTuple
}
>
<Popup>
<Link href={`/report/${report?.id}`}>
{marker.address}
</Link>
</Popup>
</Marker>
))
)}
<LocationMark /> <LocationMark />
</MapContainer> </MapContainer>
); );

View File

@ -29,6 +29,7 @@ const MapSection: React.FC<IMapSectionProps> = ({
categories = "1,2,3,4,5,6", categories = "1,2,3,4,5,6",
queryMap, queryMap,
queryRating, queryRating,
page = "1",
}: IMapSectionProps) => { }: IMapSectionProps) => {
const [mapSearch, setMapSearch] = useState<string>(queryMap || ""); const [mapSearch, setMapSearch] = useState<string>(queryMap || "");
const [latLng, setLatLng] = useState<ILatLng>({ const [latLng, setLatLng] = useState<ILatLng>({
@ -57,7 +58,9 @@ const MapSection: React.FC<IMapSectionProps> = ({
router.push( router.push(
`/?тип-дороги=${categories}${ `/?тип-дороги=${categories}${
mapSearch ? `&поиск-на-карте=${mapSearch}` : "" mapSearch ? `&поиск-на-карте=${mapSearch}` : ""
}${queryRating ? `&поиск-рейтинг=${queryRating}` : ""}`, }${
queryRating ? `&поиск-рейтинг=${queryRating}` : ""
}&страница-рейтинга=${page}`,
{ {
scroll: false, scroll: false,
} }
@ -136,7 +139,7 @@ const MapSection: React.FC<IMapSectionProps> = ({
</ul> </ul>
</div> </div>
<HomeMap data={data?.results} latLng={latLng} /> <HomeMap data={data.results} latLng={latLng} />
</section> </section>
); );
}; };

View File

@ -35,7 +35,7 @@
height: 100%; height: 100%;
display: grid; display: grid;
align-items: center; align-items: center;
grid-template-columns: 120px 200px 210px 380px 165px 92px; grid-template-columns: 120px 200px 230px 380px 165px 92px;
td { td {
button { button {
@ -63,7 +63,7 @@
height: 90px; height: 90px;
padding: 10px 0; padding: 10px 0;
display: grid; display: grid;
grid-template-columns: 120px 200px 210px 380px 165px 92px; grid-template-columns: 120px 200px 230px 380px 165px 92px;
align-items: center; align-items: center;
td { td {

View File

@ -19,6 +19,7 @@ import RoadType from "@/entities/RoadType/RoadType";
import Typography from "@/shared/ui/components/Typography/Typography"; import Typography from "@/shared/ui/components/Typography/Typography";
import Paragraph from "@/shared/ui/components/Paragraph/Paragraph"; import Paragraph from "@/shared/ui/components/Paragraph/Paragraph";
import arrows from "../../shared/icons/arrows.svg"; import arrows from "../../shared/icons/arrows.svg";
import Pagination from "@/entities/Pagination/Pagination";
interface IRatingSectionProps { interface IRatingSectionProps {
[key: string]: string; [key: string]: string;
@ -28,10 +29,12 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
categories = "1,2,3,4,5,6", categories = "1,2,3,4,5,6",
queryMap, queryMap,
queryRating, queryRating,
page = "1",
}: IRatingSectionProps) => { }: IRatingSectionProps) => {
const [ratingSearch, setRatingSearch] = useState<string>( const [ratingSearch, setRatingSearch] = useState<string>(
queryRating || "" queryRating || ""
); );
const [activePage, setActivePage] = useState<number>(+page);
const router = useRouter(); const router = useRouter();
const reports = useRatingStore(useShallow((state) => state.data)); const reports = useRatingStore(useShallow((state) => state.data));
@ -40,7 +43,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
); );
useEffect(() => { useEffect(() => {
getReports(ratingSearch); getReports(ratingSearch, +page);
}, []); }, []);
const handleSubmit: React.FormEventHandler< const handleSubmit: React.FormEventHandler<
@ -53,15 +56,36 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
router.push( router.push(
`/?тип-дороги=${categories}${ `/?тип-дороги=${categories}${
queryMap ? `&поиск-на-карте=${queryMap}` : "" queryMap ? `&поиск-на-карте=${queryMap}` : ""
}${ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : ""}`, }${
ratingSearch ? `&поиск-рейтинг=${ratingSearch}` : ""
}&страница-рейтинга=${page}`,
{ {
scroll: false, 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) => { const sliceDate = (date: string) => {
return `${date.slice(8, 10)}.${date.slice(5, 7)}.${date.slice( return `${date.slice(8, 10)}.${date.slice(5, 7)}.${date.slice(
0, 0,
@ -177,6 +201,15 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
</tbody> </tbody>
</table> </table>
</div> </div>
<Pagination
setActivePage={setActivePage}
activePage={activePage}
count={reports.count as number}
next={reports.next}
prev={reports.previous}
current_count={reports.results.length}
/>
</section> </section>
); );
}; };

View File

@ -11,7 +11,7 @@ interface IFetchReports extends IList {
interface IRatingStore extends IFetch { interface IRatingStore extends IFetch {
data: IFetchReports; data: IFetchReports;
getReports: (categories: string) => Promise<void>; getReports: (categories: string, page: number) => Promise<void>;
} }
export const useRatingStore = create<IRatingStore>((set) => ({ export const useRatingStore = create<IRatingStore>((set) => ({
@ -23,12 +23,13 @@ export const useRatingStore = create<IRatingStore>((set) => ({
}, },
isLoading: false, isLoading: false,
error: "", error: "",
getReports: async (query: string = "") => { getReports: async (query: string = "", page: number = 1) => {
try { try {
set({ isLoading: true }); set({ isLoading: true });
const data = (await apiInstance.get<IFetchReports>("/report/")) const data = (
.data; await apiInstance.get<IFetchReports>(`/report/?page=${page}`)
).data;
const searched = data.results.filter((rating) => { const searched = data.results.filter((rating) => {
return rating.location.some((location) => { return rating.location.some((location) => {