fixed bugs with sort

This commit is contained in:
Alibek 2024-03-10 04:38:46 +06:00
parent bcc7279d39
commit 74b15060a1
18 changed files with 162 additions and 291 deletions

View File

@ -182,5 +182,11 @@
"invalid_activation_code_reset": "Invalid activation code for reset.", "invalid_activation_code_reset": "Invalid activation code for reset.",
"invalid_password_reset_code": "Invalid password reset code.", "invalid_password_reset_code": "Invalid password reset code.",
"invalid_code": "Invalid code." "invalid_code": "Invalid code."
},
"disclaimer": {
"text": "This website is funded by the European Union. Its contents are the sole responsibility of Transparency International Kyrgyzstan and do not necessarily reflect the views of the European Union."
},
"rights": {
"text": "All rights reserved"
} }
} }

View File

@ -182,5 +182,11 @@
"invalid_activation_code_reset": "Сыр сөздү калыпта тапшыруу үчүн четке калган иштеш коду.", "invalid_activation_code_reset": "Сыр сөздү калыпта тапшыруу үчүн четке калган иштеш коду.",
"invalid_password_reset_code": "Сыр сөздү өзгөртүү коду четке калган эмес.", "invalid_password_reset_code": "Сыр сөздү өзгөртүү коду четке калган эмес.",
"invalid_code": "Четке калган иштеш коду." "invalid_code": "Четке калган иштеш коду."
},
"disclaimer": {
"text": "Бул веб-сайт Европа Биримдиги тарабынан каржыланат. Анын мазмуну үчүн Трансперенси Интернешнл Кыргызстан гана жоопкерчиликтүү жана ал Европа Биримдигинин көз карашын сөзсүз түрдө чагылдырбайт."
},
"rights": {
"text": "Бардык укуктар корголгон"
} }
} }

View File

@ -182,5 +182,11 @@
"invalid_activation_code_reset": "Код активации не действителен.", "invalid_activation_code_reset": "Код активации не действителен.",
"invalid_password_reset_code": "Код сброса пароля не действителен.", "invalid_password_reset_code": "Код сброса пароля не действителен.",
"invalid_code": "Неверный код." "invalid_code": "Неверный код."
},
"disclaimer": {
"text": "Этот веб-сайт финансируется Европейским Союзом. Ответственность за его содержание лежит исключительно на Трансперенси Интернешнл Кыргызстан и не обязательно отражает точку зрения Европейского Союза."
},
"rights": {
"text": "Все права защищены"
} }
} }

View File

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start -p 8004", "start": "next start -p 3000",
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {

View File

@ -15,3 +15,17 @@ export interface IReport {
total_likes: number; total_likes: number;
count_reviews: number; count_reviews: number;
} }
export interface IRatingReport {
created_at: string;
category: null;
author: {
id: number;
first_name: string;
last_name: string;
govern_status: null;
};
total_likes: number;
count_reviews: number;
address: string;
}

View File

@ -4,6 +4,19 @@
grid-template-columns: 1fr 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 30px; gap: 30px;
background-color: rgb(15, 23, 42); background-color: rgb(15, 23, 42);
&__logo {
display: flex;
flex-direction: column;
gap: 24px;
p {
color: white;
font-family: "Inter", sans-serif;
font-size: 16px;
}
}
a, a,
li, li,
h4 { h4 {

View File

@ -13,11 +13,17 @@ import NetKgTracker from "@/widgets/NetKgTracker/NetKgTracker";
const Footer = () => { const Footer = () => {
const t = useTranslations("general"); const t = useTranslations("general");
const tDisclaimer = useTranslations("disclaimer");
const tRights = useTranslations("rights");
return ( return (
<footer className="footer"> <footer className="footer">
<div className="footer__logo">
<Link href="/"> <Link href="/">
<Image src={logo} alt="Logo" /> <Image src={logo} alt="Logo" />
</Link> </Link>
<p>© {tRights("text")}</p>
<p>{tDisclaimer("text")}</p>
</div>
<div className="footer__links"> <div className="footer__links">
<h4>{t("navigation")}</h4> <h4>{t("navigation")}</h4>
<ul> <ul>
@ -45,8 +51,8 @@ const Footer = () => {
</Link> </Link>
))} ))}
</li> </li>
<li>Photo By ThomasG, CC BY-SA 3.0</li>
</ul> </ul>
<p className="text-white">Photo By ThomasG, CC BY-SA 3.0</p>
</div> </div>
<div className="footer__apps"> <div className="footer__apps">

View File

@ -11,6 +11,18 @@
align-items: center; align-items: center;
background-color: #fff; background-color: #fff;
&__logo {
display: flex;
align-items: center;
gap: 10px;
&_last {
min-width: 78px;
max-width: 78px;
height: 100%;
}
}
&__links { &__links {
height: 40%; height: 40%;
display: flex; display: flex;
@ -46,6 +58,16 @@
} }
} }
@media screen and (max-width: 1220px) {
.navbar {
padding: 0 60px;
&__links {
gap: 40px;
}
}
}
@media screen and (max-width: 1024px) { @media screen and (max-width: 1024px) {
.navbar { .navbar {
padding: 0 30px; padding: 0 30px;
@ -60,7 +82,7 @@
} }
} }
@media screen and (max-width: 768px) { @media screen and (max-width: 900px) {
.navbar { .navbar {
height: 72px; height: 72px;

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import "./Navbar.scss"; import "./Navbar.scss";
import Image from "next/image"; import Image, { StaticImageData } from "next/image";
import { Link } from "@/shared/config/navigation"; import { Link } from "@/shared/config/navigation";
import { usePathname } from "@/shared/config/navigation"; import { usePathname } from "@/shared/config/navigation";
import logo from "@/shared/assets/logo.svg"; import logo from "@/shared/assets/logo.svg";
@ -12,16 +12,34 @@ import menu from "./icons/menu.svg";
import cross from "./icons/cross.svg"; import cross from "./icons/cross.svg";
import NavMenu from "./NavMenu/NavMenu"; import NavMenu from "./NavMenu/NavMenu";
import { useState } from "react"; import { useState } from "react";
import founded_ru from "./assets/founded-ru.png";
import founded_en from "./assets/founded-en.png";
import founded_kg from "./assets/founded-kg.png";
import { useParams } from "next/navigation";
const Navbar = () => { const Navbar = () => {
const pathname = usePathname(); const pathname = usePathname();
const [openMenu, setOpenMenu] = useState<boolean>(false); const [openMenu, setOpenMenu] = useState<boolean>(false);
const { locale } = useParams();
const FOUNDED: Record<string, StaticImageData> = {
ru: founded_ru,
kg: founded_kg,
en: founded_en,
};
return ( return (
<section className="navbar"> <section className="navbar">
<div className="navbar__logo">
<Link href="/"> <Link href="/">
<Image src={logo} alt="Logo" /> <Image src={logo} alt="Logo" />
</Link> </Link>
<Image
className="navbar__logo_last"
src={FOUNDED[locale as string]}
alt="Founded by EU Image"
/>
</div>
<nav className="navbar__links"> <nav className="navbar__links">
{LINKS().map((link) => ( {LINKS().map((link) => (

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@ -39,10 +39,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
const [activePage, setActivePage] = useState<number>( const [activePage, setActivePage] = useState<number>(
+searchParams["страница-рейтинга"] || 1 +searchParams["страница-рейтинга"] || 1
); );
const [filter, setFilter] = useState({ const [sort, setSort] = useState<string>("");
option: "date",
toggle: false,
});
const router = useRouter(); const router = useRouter();
const reports = useRatingStore(useShallow((state) => state.data)); const reports = useRatingStore(useShallow((state) => state.data));
@ -51,8 +48,8 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
); );
useEffect(() => { useEffect(() => {
getReports(ratingSearch, activePage, filter); getReports(ratingSearch, activePage, sort);
}, []); }, [sort]);
const handleSubmit: React.FormEventHandler< const handleSubmit: React.FormEventHandler<
HTMLFormElement HTMLFormElement
@ -74,7 +71,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
} }
); );
getReports(ratingSearch, activePage, filter); getReports(ratingSearch, activePage, sort);
if ( if (
reports.results.length < 8 && reports.results.length < 8 &&
@ -98,7 +95,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
} }
); );
getReports(ratingSearch, activePage, filter); getReports(ratingSearch, activePage, sort);
}, [activePage]); }, [activePage]);
const params = [ const params = [
@ -106,14 +103,7 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
id: 1, id: 1,
name: tGeneral("date"), name: tGeneral("date"),
handleClick() { handleClick() {
if (filter.option !== "date") { setSort("");
return setFilter({ option: "date", toggle: false });
}
setFilter((prev) => {
return { option: "date", toggle: !prev.toggle };
});
getReports(ratingSearch, activePage, filter);
}, },
}, },
{ id: 2, name: tGeneral("address") }, { id: 2, name: tGeneral("address") },
@ -123,28 +113,14 @@ const RatingSection: React.FC<IRatingSectionProps> = ({
id: 5, id: 5,
name: tGeneral("reviews"), name: tGeneral("reviews"),
handleClick() { handleClick() {
if (filter.option !== "reviews") { setSort("reviews");
return setFilter({ option: "reviews", toggle: false });
}
setFilter((prev) => {
return { option: "reviews", toggle: !prev.toggle };
});
getReports(ratingSearch, activePage, filter);
}, },
}, },
{ {
id: 6, id: 6,
name: tGeneral("rating"), name: tGeneral("rating"),
handleClick() { handleClick() {
if (filter.option !== "rating") { setSort("likes");
return setFilter({ option: "rating", toggle: false });
}
setFilter((prev) => {
return { option: "rating", toggle: !prev.toggle };
});
getReports(ratingSearch, activePage, filter);
}, },
}, },
]; ];

View File

@ -13,7 +13,7 @@ interface IRatingStore extends IFetch {
getReports: ( getReports: (
categories: string, categories: string,
page: number, page: number,
filter: { option: string; toggle: boolean } sort: string
) => Promise<void>; ) => Promise<void>;
} }
@ -29,14 +29,14 @@ export const useRatingStore = create<IRatingStore>((set) => ({
getReports: async ( getReports: async (
query: string = "", query: string = "",
page: number = 1, page: number = 1,
filter: { option: string; toggle: boolean } sort: string = ""
) => { ) => {
try { try {
set({ isLoading: true }); set({ isLoading: true });
const data = ( const data = (
await apiInstance.get<IFetchReports>( await apiInstance.get<IFetchReports>(
`/report/?page=${page}&page_size=${8}` `/report/list_sort?sort_by=${sort}&page=${page}`
) )
).data; ).data;
@ -50,46 +50,6 @@ export const useRatingStore = create<IRatingStore>((set) => ({
data.results = [...searched]; data.results = [...searched];
if (filter.option === "date" && filter.toggle === false) {
data.results = data.results.sort((a, b) => {
const dateA = new Date(a.created_at) as unknown as number;
const dateB = new Date(b.created_at) as unknown as number;
return dateA - dateB;
});
} else if (filter.option === "date" && filter.toggle === true) {
data.results = data.results.sort((a, b) => {
const dateA = new Date(a.created_at) as unknown as number;
const dateB = new Date(b.created_at) as unknown as number;
return dateB - dateA;
});
}
if (filter.option === "reviews" && filter.toggle === false) {
data.results = data.results.sort(
(a, b) => a.count_reviews - b.count_reviews
);
} else if (
filter.option === "reviews" &&
filter.toggle === true
) {
data.results = data.results.sort(
(a, b) => b.count_reviews - a.count_reviews
);
}
if (filter.option === "rating" && filter.toggle === false) {
data.results = data.results.sort(
(a, b) => a.total_likes - b.total_likes
);
} else if (
filter.option === "rating" &&
filter.toggle === true
) {
data.results = data.results.sort(
(a, b) => b.total_likes - a.total_likes
);
}
set({ data: data }); set({ data: data });
} catch (error: any) { } catch (error: any) {
set({ error: error.message }); set({ error: error.message });

View File

@ -29,10 +29,7 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
} = useProfileReportsStore(); } = useProfileReportsStore();
const session = useSession(); const session = useSession();
const router = useRouter(); const router = useRouter();
const [filter, setFilter] = useState({ const [sort, setSort] = useState("date");
option: "date",
toggle: true,
});
const [activePage, setActivePage] = useState<number>( const [activePage, setActivePage] = useState<number>(
+searchParams["страница-обращений"] || 1 +searchParams["страница-обращений"] || 1
); );
@ -40,18 +37,7 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
{ {
param: "Дата", param: "Дата",
handleClick() { handleClick() {
if (filter.option !== "date") { setSort("date");
return setFilter({ option: "date", toggle: false });
}
setFilter((prev) => {
return { option: "date", toggle: !prev.toggle };
});
getMyReports(
filter,
activePage,
session.data?.access_token as string
);
}, },
}, },
{ param: "Адрес" }, { param: "Адрес" },
@ -59,38 +45,13 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
{ {
param: "Комментарии", param: "Комментарии",
handleClick() { handleClick() {
if (filter.option !== "count_reviews") { setSort("reviews");
return setFilter({
option: "count_reviews",
toggle: false,
});
}
setFilter((prev) => {
return { option: "count_reviews", toggle: !prev.toggle };
});
getMyReports(
filter,
activePage,
session.data?.access_token as string
);
}, },
}, },
{ {
param: "Рейтинг", param: "Рейтинг",
handleClick() { handleClick() {
if (filter.option !== "total_likes") { setSort("rating");
return setFilter({ option: "total_likes", toggle: false });
}
setFilter((prev) => {
return { option: "total_likes", toggle: !prev.toggle };
});
getMyReports(
filter,
activePage,
session.data?.access_token as string
);
}, },
}, },
]; ];
@ -98,7 +59,7 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
useEffect(() => { useEffect(() => {
if (session.status === "loading") return; if (session.status === "loading") return;
getMyReports( getMyReports(
filter, sort,
activePage, activePage,
session.data?.access_token as string session.data?.access_token as string
); );
@ -110,11 +71,11 @@ const ProfileTable: React.FC<IProfileTableProps> = ({
{ scroll: false } { scroll: false }
); );
getMyReports( getMyReports(
filter, sort,
activePage, activePage,
session.data?.access_token as string session.data?.access_token as string
); );
}, [activePage]); }, [activePage, sort]);
return ( return (
<div className="profile-table"> <div className="profile-table">

View File

@ -15,7 +15,7 @@ const filterCategories: Record<string, string> = {
interface IProfileReportsStore extends IFetch { interface IProfileReportsStore extends IFetch {
data: IMyReportsList; data: IMyReportsList;
getMyReports: ( getMyReports: (
filter: { option: string; toggle: boolean }, filter: string,
page: number, page: number,
access_token: string access_token: string
) => void; ) => void;
@ -32,10 +32,7 @@ export const useProfileReportsStore = create<IProfileReportsStore>(
results: [], results: [],
}, },
getMyReports: async ( getMyReports: async (
filter: { sort: string,
option: string;
toggle: boolean;
},
page: number, page: number,
access_token: string access_token: string
) => { ) => {
@ -47,48 +44,27 @@ export const useProfileReportsStore = create<IProfileReportsStore>(
}, },
}; };
set({ isLoading: true }); set({ isLoading: true });
const res = await apiInstance.get<IMyReportsList>( const data = await apiInstance
`/users/reports/?page=${page}`, .get<IMyReportsList>(
`/users/reports/?page=${page}&page_size=8`,
config config
); )
.then((res) => res.data);
let data = res.data; if (sort === "date") {
if (filter.option === "date" && filter.toggle === false) {
data.results = data.results.sort((a, b) => { data.results = data.results.sort((a, b) => {
const dateA = new Date(a.created_at) as unknown as number; const dateA = new Date(b.created_at) as unknown as number;
const dateB = new Date(b.created_at) as unknown as number; const dateB = new Date(a.created_at) as unknown as number;
return dateA - dateB; return dateA - dateB;
}); });
} else if ( } else if (sort === "reviews") {
filter.option === "date" && data.results = data.results.sort(
filter.toggle === true (a, b) => b.count_reviews - a.count_reviews
) { );
data.results = data.results.sort((a, b) => { } else if (sort === "rating") {
const dateA = new Date(a.created_at) as unknown as number; data.results = data.results.sort(
const dateB = new Date(b.created_at) as unknown as number; (a, b) => b.total_likes - a.total_likes
return dateB - dateA; );
});
}
if (
filter.option === filterCategories[filter.option] &&
filter.toggle === false
) {
data.results = data.results.sort((a, b) => {
const optionKey = filter.option as keyof IMyReports;
return (Number(a[optionKey]) -
Number(b[optionKey])) as number;
});
} else if (
filter.option === "rating" &&
filter.toggle === true
) {
data.results = data.results.sort((a, b) => {
const optionKey = filter.option as keyof IMyReports;
return (Number(a[optionKey]) -
Number(b[optionKey])) as number;
});
} }
set({ data: data }); set({ data: data });

View File

@ -32,10 +32,7 @@ const StatisticsTable: React.FC<IStatisticsTableProps> = ({
getStatistics, getStatistics,
data: statistics, data: statistics,
} = useStatisticsStore(useShallow((state) => state)); } = useStatisticsStore(useShallow((state) => state));
const [filter, setFilter] = useState({ const [sort, setSort] = useState<string>("broken_road_1");
option: "broken_road",
toggle: false,
});
const [openPopup, setOpenPopup] = useState<boolean>(false); const [openPopup, setOpenPopup] = useState<boolean>(false);
@ -44,122 +41,54 @@ const StatisticsTable: React.FC<IStatisticsTableProps> = ({
const params = [ const params = [
{ {
param: "Добавлено дорог", param: "Добавлено дорог",
async handleClick() { handleClick() {
if (filter.option !== "broken_road_1") { setSort("broken_road_1");
return setFilter({
option: "broken_road_1",
toggle: false,
});
}
setFilter((prev) => {
return { option: "broken_road_1", toggle: !prev.toggle };
});
getStatistics(location, filter, query);
}, },
}, },
{ {
param: "Локальных дефектов", param: "Локальных дефектов",
async handleClick() { handleClick() {
if (filter.option !== "local_defect_3") { setSort("local_defect_3");
return setFilter({
option: "local_defect_3",
toggle: false,
});
}
setFilter((prev) => {
return { option: "local_defect_3", toggle: !prev.toggle };
});
getStatistics(location, filter, query);
}, },
}, },
{ {
param: "Очагов аварийности", param: "Очагов аварийности",
async handleClick() { handleClick() {
if (filter.option !== "hotbed_of_accidents_2") { setSort("hotbed_of_accidents_2");
return setFilter({
option: "hotbed_of_accidents_2",
toggle: false,
});
}
setFilter((prev) => {
return {
option: "hotbed_of_accidents_2",
toggle: !prev.toggle,
};
});
getStatistics(location, filter, query);
}, },
}, },
{ {
param: "Локальных дефектов исправлено", param: "Локальных дефектов исправлено",
async handleClick() { handleClick() {
if (filter.option !== "local_defect_fixed_6") { setSort("local_defect_fixed_6");
return setFilter({
option: "local_defect_fixed_6",
toggle: false,
});
}
setFilter((prev) => {
return {
option: "local_defect_fixed_6",
toggle: !prev.toggle,
};
});
getStatistics(location, filter, query);
}, },
}, },
{ {
param: "В планах ремонта", param: "В планах ремонта",
async handleClick() { handleClick() {
if (filter.option !== "repair_plans_4") { setSort("repair_plans_4");
return setFilter({
option: "repair_plans_4",
toggle: false,
});
}
setFilter((prev) => {
return { option: "repair_plans_4", toggle: !prev.toggle };
});
getStatistics(location, filter, query);
}, },
}, },
{ {
param: "Отремонтировано", param: "Отремонтировано",
async handleClick() { handleClick() {
if (filter.option !== "repaired_5") { setSort("repaired_5");
return setFilter({ option: "repaired_5", toggle: false });
}
setFilter((prev) => {
return { option: "repaired_5", toggle: !prev.toggle };
});
getStatistics(location, filter, query);
}, },
}, },
]; ];
const getStatsByLocation = async (location: string) => { const getStatsByLocation = async (location: string) => {
setLocation(location); setLocation(location);
getStatistics(location, filter, query); getStatistics(location, sort, query);
console.error("Error fetching statistics:", error);
}; };
useEffect(() => { useEffect(() => {
getStatistics(location, filter, query); getStatistics(location, sort, query);
router.push(`/statistics?поиск-населенного-пункта=${query}`); router.push(`/statistics?поиск-населенного-пункта=${query}`, {
}, [query]); scroll: false,
});
}, [query, sort]);
return ( return (
<div className="statistics-table"> <div className="statistics-table">
@ -241,11 +170,7 @@ const StatisticsTable: React.FC<IStatisticsTableProps> = ({
)) ))
) : ( ) : (
<tr id="statistics-table__no-data-warning"> <tr id="statistics-table__no-data-warning">
<td> <td>{error ? error : "Данных не найдено"}</td>
{error
? error
: "Oops, looks like there is no data"}
</td>
</tr> </tr>
)} )}
</tbody> </tbody>

View File

@ -18,7 +18,7 @@ interface IStatisticsStore extends IFetch {
data: IStatistics[]; data: IStatistics[];
getStatistics: ( getStatistics: (
endpoint: string, endpoint: string,
filter: { option: string; toggle: boolean }, sort: string,
query: string query: string
) => void; ) => void;
} }
@ -29,7 +29,7 @@ export const useStatisticsStore = create<IStatisticsStore>((set) => ({
data: [], data: [],
getStatistics: async ( getStatistics: async (
endpoint: string, endpoint: string,
filter: { option: string; toggle: boolean }, sort: string,
query: string = "" query: string = ""
) => { ) => {
try { try {
@ -42,27 +42,9 @@ export const useStatisticsStore = create<IStatisticsStore>((set) => ({
loc.name.toLowerCase().includes(query.toLowerCase()) loc.name.toLowerCase().includes(query.toLowerCase())
); );
if ( const sorted = data.sort((a: any, b: any) => a[sort] - b[sort]);
filter.option === filterCategories[filter.option] &&
filter.toggle === false
) {
data = data.sort((a, b) => {
const optionKey = filter.option as keyof IStatistics;
return (Number(a[optionKey]) -
Number(b[optionKey])) as number;
});
} else if (
filter.option === "rating" &&
filter.toggle === true
) {
data = data.sort((a, b) => {
const optionKey = filter.option as keyof IStatistics;
return (Number(a[optionKey]) -
Number(b[optionKey])) as number;
});
}
set({ data: data }); set({ data: sorted });
} catch (error: unknown) { } catch (error: unknown) {
if (error instanceof AxiosError) { if (error instanceof AxiosError) {
set({ error: error.message }); set({ error: error.message });