forked from Transparency/kgroad-frontend2
made pagination and fixed map
This commit is contained in:
parent
ed72067108
commit
a12aaa1213
@ -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["страница-рейтинга"]}
|
||||
/>
|
||||
<RatingSection
|
||||
categories={searchParams["тип-дороги"]}
|
||||
queryMap={searchParams["поиск-на-карте"]}
|
||||
queryRating={searchParams["поиск-рейтинг"]}
|
||||
page={searchParams["страница-рейтинга"]}
|
||||
/>
|
||||
<NewsSection />
|
||||
</div>
|
||||
|
40
src/entities/Pagination/Pagination.scss
Normal file
40
src/entities/Pagination/Pagination.scss
Normal 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);
|
||||
}
|
||||
}
|
140
src/entities/Pagination/Pagination.tsx
Normal file
140
src/entities/Pagination/Pagination.tsx
Normal 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;
|
7
src/entities/Pagination/icons/arrow-left.svg
Normal file
7
src/entities/Pagination/icons/arrow-left.svg
Normal 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 |
7
src/entities/Pagination/icons/arrow-right.svg
Normal file
7
src/entities/Pagination/icons/arrow-right.svg
Normal 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 |
@ -1,6 +1,6 @@
|
||||
export interface ILocation {
|
||||
id: 4;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
address: string;
|
||||
}
|
||||
|
@ -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<IHomeMapProps> = ({
|
||||
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<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 = () => {
|
||||
@ -76,6 +91,8 @@ const HomeMap: React.FC<IHomeMapProps> = ({
|
||||
return null;
|
||||
};
|
||||
|
||||
const defPos = [42.8746, 74.606];
|
||||
|
||||
return (
|
||||
<MapContainer
|
||||
center={position}
|
||||
@ -87,24 +104,43 @@ const HomeMap: React.FC<IHomeMapProps> = ({
|
||||
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
/>
|
||||
{data?.map((marker) => (
|
||||
|
||||
{data.map((report) =>
|
||||
report.location.length === 2 ? (
|
||||
<Polyline
|
||||
key={report.id}
|
||||
pathOptions={categoryToPolyline[report.category]}
|
||||
positions={[
|
||||
[
|
||||
parseFloat(report.location[0].latitude),
|
||||
parseFloat(report.location[0].longitude),
|
||||
],
|
||||
[
|
||||
parseFloat(report.location[1].latitude),
|
||||
parseFloat(report.location[1].longitude),
|
||||
],
|
||||
]}
|
||||
/>
|
||||
) : null
|
||||
)}
|
||||
|
||||
{data?.map((report) =>
|
||||
report.location.map((marker) => (
|
||||
<Marker
|
||||
key={marker.id}
|
||||
icon={icons[marker.category]}
|
||||
icon={icons[report.category]}
|
||||
position={
|
||||
[
|
||||
+marker.location[0].latitude,
|
||||
+marker.location[0].longitude,
|
||||
] as LatLngTuple
|
||||
[+marker.latitude, +marker.longitude] as LatLngTuple
|
||||
}
|
||||
>
|
||||
<Popup>
|
||||
<Link href={`/report/${marker.location[0].id}`}>
|
||||
{marker.location[0].address}
|
||||
<Link href={`/report/${report?.id}`}>
|
||||
{marker.address}
|
||||
</Link>
|
||||
</Popup>
|
||||
</Marker>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
<LocationMark />
|
||||
</MapContainer>
|
||||
);
|
||||
|
@ -29,6 +29,7 @@ const MapSection: React.FC<IMapSectionProps> = ({
|
||||
categories = "1,2,3,4,5,6",
|
||||
queryMap,
|
||||
queryRating,
|
||||
page = "1",
|
||||
}: IMapSectionProps) => {
|
||||
const [mapSearch, setMapSearch] = useState<string>(queryMap || "");
|
||||
const [latLng, setLatLng] = useState<ILatLng>({
|
||||
@ -57,7 +58,9 @@ const MapSection: React.FC<IMapSectionProps> = ({
|
||||
router.push(
|
||||
`/?тип-дороги=${categories}${
|
||||
mapSearch ? `&поиск-на-карте=${mapSearch}` : ""
|
||||
}${queryRating ? `&поиск-рейтинг=${queryRating}` : ""}`,
|
||||
}${
|
||||
queryRating ? `&поиск-рейтинг=${queryRating}` : ""
|
||||
}&страница-рейтинга=${page}`,
|
||||
{
|
||||
scroll: false,
|
||||
}
|
||||
@ -136,7 +139,7 @@ const MapSection: React.FC<IMapSectionProps> = ({
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<HomeMap data={data?.results} latLng={latLng} />
|
||||
<HomeMap data={data.results} latLng={latLng} />
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -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 {
|
||||
|
@ -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<IRatingSectionProps> = ({
|
||||
categories = "1,2,3,4,5,6",
|
||||
queryMap,
|
||||
queryRating,
|
||||
page = "1",
|
||||
}: IRatingSectionProps) => {
|
||||
const [ratingSearch, setRatingSearch] = useState<string>(
|
||||
queryRating || ""
|
||||
);
|
||||
const [activePage, setActivePage] = useState<number>(+page);
|
||||
const router = useRouter();
|
||||
|
||||
const reports = useRatingStore(useShallow((state) => state.data));
|
||||
@ -40,7 +43,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getReports(ratingSearch);
|
||||
getReports(ratingSearch, +page);
|
||||
}, []);
|
||||
|
||||
const handleSubmit: React.FormEventHandler<
|
||||
@ -53,15 +56,36 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
|
||||
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<IRatingSectionProps> = ({
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
setActivePage={setActivePage}
|
||||
activePage={activePage}
|
||||
count={reports.count as number}
|
||||
next={reports.next}
|
||||
prev={reports.previous}
|
||||
current_count={reports.results.length}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
@ -11,7 +11,7 @@ interface IFetchReports extends IList {
|
||||
|
||||
interface IRatingStore extends IFetch {
|
||||
data: IFetchReports;
|
||||
getReports: (categories: string) => Promise<void>;
|
||||
getReports: (categories: string, page: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useRatingStore = create<IRatingStore>((set) => ({
|
||||
@ -23,12 +23,13 @@ export const useRatingStore = create<IRatingStore>((set) => ({
|
||||
},
|
||||
isLoading: false,
|
||||
error: "",
|
||||
getReports: async (query: string = "") => {
|
||||
getReports: async (query: string = "", page: number = 1) => {
|
||||
try {
|
||||
set({ isLoading: true });
|
||||
|
||||
const data = (await apiInstance.get<IFetchReports>("/report/"))
|
||||
.data;
|
||||
const data = (
|
||||
await apiInstance.get<IFetchReports>(`/report/?page=${page}`)
|
||||
).data;
|
||||
|
||||
const searched = data.results.filter((rating) => {
|
||||
return rating.location.some((location) => {
|
||||
|
Loading…
Reference in New Issue
Block a user