Add tender details page
This commit is contained in:
parent
7eedb5ec39
commit
1d68743bc8
144
src/app/[locale]/tenders/[id]/page.tsx
Normal file
144
src/app/[locale]/tenders/[id]/page.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import { apiInstance } from "@/shared/config/apiConfig";
|
||||||
|
import { ITenders } from "@/shared/types/tenders-type";
|
||||||
|
import { Container, Title } from "@/shared/ui";
|
||||||
|
|
||||||
|
const TenderDetails = async ({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { id: string; новость: string };
|
||||||
|
}) => {
|
||||||
|
const getTendersById = async () => {
|
||||||
|
const response = await apiInstance.get<ITenders>(
|
||||||
|
`/procurements/${params.id}/`
|
||||||
|
);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = await getTendersById();
|
||||||
|
|
||||||
|
const formatNumber = (number: number | string): string => {
|
||||||
|
if (typeof number === "string") {
|
||||||
|
number = parseFloat(number);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(number)) return "";
|
||||||
|
|
||||||
|
return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||||
|
};
|
||||||
|
|
||||||
|
const months: Record<string, string> = {
|
||||||
|
"01": "Январь",
|
||||||
|
"02": "Февраль",
|
||||||
|
"03": "Март",
|
||||||
|
"04": "Апрель",
|
||||||
|
"05": "Май",
|
||||||
|
"06": "Июнь",
|
||||||
|
"07": "Июль",
|
||||||
|
"08": "Август",
|
||||||
|
"09": "Сентябрь",
|
||||||
|
"10": "Октябрь",
|
||||||
|
"11": "Ноябрь",
|
||||||
|
"12": "Декабрь",
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Title
|
||||||
|
text={`№ ${data.id_of_card}`}
|
||||||
|
size="lg"
|
||||||
|
className="font-bold text-[24px] mb-[30px]"
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col gap-[27px] w-full mb-[83px]">
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Наименование закупки"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title
|
||||||
|
text={data.name_of_buy}
|
||||||
|
className="text-light-blue text-[20px] leading-8 font-semibold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Закупающая организация"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title
|
||||||
|
text={data.name_of_organization}
|
||||||
|
className="text-light-blue text-[20px] leading-8 font-semibold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Планируемая сумма"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title
|
||||||
|
text={formatNumber(data.plan_summ)}
|
||||||
|
className="text-light-blue text-[20px] leading-8 font-semibold"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Статус"
|
||||||
|
className="text-[18px] text-gray-50 w-[242px] "
|
||||||
|
/>
|
||||||
|
<Title text={"Статус"} className=" text-[20px] leading-8" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Дата публикации"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<p className="text-[20px] leading-8">
|
||||||
|
{data.date_of_publication_datetime.slice(8, 10)}{" "}
|
||||||
|
{months[data.date_of_publication_datetime.slice(5, 7)]}{" "}
|
||||||
|
{data.date_of_publication_datetime.slice(0, 4)}
|
||||||
|
</p>
|
||||||
|
{months[data.date_of_publication_datetime.slice(0, 5)]}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Окончание приема заявок"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<p className="text-[20px] leading-8">
|
||||||
|
{data.date_of_publication_datetime.slice(8, 10)}{" "}
|
||||||
|
{months[data.date_of_publication_datetime.slice(5, 7)]}{" "}
|
||||||
|
{data.date_of_publication_datetime.slice(0, 4)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Рабочий телефон"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title text={data.tel_number} className="text-[20px] leading-8" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Больше информации"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title text={data.more_info_url} className="text-[20px] leading-8" />
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-[108px]">
|
||||||
|
<Title
|
||||||
|
text="Больше информации в PDF"
|
||||||
|
className="text-[18px] text-gray-500 w-[242px]"
|
||||||
|
/>
|
||||||
|
<Title text={data.more_info_pdf} className="text-[20px] leading-8" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Title
|
||||||
|
text={"Лоты"}
|
||||||
|
size="lg"
|
||||||
|
className="font-bold text-[24px] mb-[30px]"
|
||||||
|
/>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TenderDetails;
|
@ -2,7 +2,13 @@ import { Container, Title } from "@/shared/ui";
|
|||||||
import TendersList from "@/widgets/TendersList/TendersList";
|
import TendersList from "@/widgets/TendersList/TendersList";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
const Tenders = () => {
|
const Tenders = ({
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
searchParams: {
|
||||||
|
["страница-тендеров"]: string;
|
||||||
|
};
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#FAFCFF]">
|
<div className="bg-[#FAFCFF]">
|
||||||
<Container>
|
<Container>
|
||||||
@ -11,7 +17,7 @@ const Tenders = () => {
|
|||||||
size="md"
|
size="md"
|
||||||
className="text-lg font-bold mb-[39px] pt-[101px]"
|
className="text-lg font-bold mb-[39px] pt-[101px]"
|
||||||
/>
|
/>
|
||||||
<TendersList />
|
<TendersList searchParams={searchParams} />
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Link } from "@/shared/config/navigation";
|
||||||
import { Title } from "@/shared/ui";
|
import { Title } from "@/shared/ui";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
@ -30,9 +31,13 @@ const TenderCard: React.FC<Props> = ({
|
|||||||
text={`№ ${id_of_card}`}
|
text={`№ ${id_of_card}`}
|
||||||
className="font-bold text-[24px] mb-[48px]"
|
className="font-bold text-[24px] mb-[48px]"
|
||||||
/>
|
/>
|
||||||
|
<Link
|
||||||
|
href={{ pathname: `/tenders/${id}`, query: { тендер: name_of_buy } }}
|
||||||
|
>
|
||||||
<h2 className="mb-6 text-[18px] font-semibold leading-6 text-blue">
|
<h2 className="mb-6 text-[18px] font-semibold leading-6 text-blue">
|
||||||
{name_of_buy}
|
{name_of_buy}
|
||||||
</h2>
|
</h2>
|
||||||
|
</Link>
|
||||||
<h2 className="text-[18px] leading-6 font-normal ">
|
<h2 className="text-[18px] leading-6 font-normal ">
|
||||||
{name_of_organization}
|
{name_of_organization}
|
||||||
</h2>
|
</h2>
|
||||||
|
@ -37,12 +37,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__page_active {
|
&__page_active {
|
||||||
background-color: rgb(249, 250, 251) !important;
|
background-color: rgb(236, 243, 250) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__page:hover,
|
&__page:hover,
|
||||||
&__prev:hover,
|
&__prev:hover,
|
||||||
&__next:hover {
|
&__next:hover {
|
||||||
background-color: rgb(249, 250, 251);
|
background-color: rgb(229, 230, 230);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,24 @@ import Image from "next/image";
|
|||||||
import search from "./icons/search.svg";
|
import search from "./icons/search.svg";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const SearchForm = () => {
|
interface ISearchFormProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
handleSubmit: (e: React.MouseEvent<HTMLFormElement>) => void;
|
||||||
|
}
|
||||||
|
const SearchForm: React.FC<ISearchFormProps> = ({
|
||||||
|
handleSubmit,
|
||||||
|
name,
|
||||||
|
placeholder,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
style,
|
||||||
|
}: ISearchFormProps) => {
|
||||||
const t = useTranslations("general");
|
const t = useTranslations("general");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="mb-8 h-12 w-full relative flex items-center">
|
<form
|
||||||
|
className="mb-8 h-12 w-full relative flex items-center"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
<div className="h-full flex flex-1 items-center border border-black rounded-sm bg-white">
|
<div className="h-full flex flex-1 items-center border border-black rounded-sm bg-white">
|
||||||
<Image
|
<Image
|
||||||
className="w-12 h-12 p-2"
|
className="w-12 h-12 p-2"
|
||||||
@ -16,7 +29,10 @@ const SearchForm = () => {
|
|||||||
alt="Search button icon"
|
alt="Search button icon"
|
||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
placeholder="Ключевое слово или номер"
|
onChange={onChange}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
name={name}
|
||||||
type="text"
|
type="text"
|
||||||
className="px-3 pl-2 w-full h-full text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
className="px-3 pl-2 w-full h-full text-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||||
aria-label={t("search")}
|
aria-label={t("search")}
|
||||||
|
@ -10,6 +10,10 @@ export interface ITenders {
|
|||||||
date_of_publication_datetime: string;
|
date_of_publication_datetime: string;
|
||||||
date_of_offer_datetime: string;
|
date_of_offer_datetime: string;
|
||||||
current_timestamp: string;
|
current_timestamp: string;
|
||||||
|
plan_summ: string;
|
||||||
|
tel_number: string;
|
||||||
|
more_info_url: string;
|
||||||
|
more_info_pdf: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITendersList extends IList {
|
export interface ITendersList extends IList {
|
||||||
|
@ -1,24 +1,32 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTendersStore } from "./tendersStore";
|
import { useTendersStore } from "./tendersStore";
|
||||||
import { data } from "autoprefixer";
|
|
||||||
import TenderCard from "@/entities/TenderCard";
|
import TenderCard from "@/entities/TenderCard";
|
||||||
import Pagination from "@/features/Pagination/Pagination";
|
import Pagination from "@/features/Pagination/Pagination";
|
||||||
import SearchForm from "@/features/SearchForm/SearchForm";
|
import SearchForm from "@/features/SearchForm/SearchForm";
|
||||||
|
|
||||||
interface Props {}
|
interface Props {
|
||||||
const TendersList = () => {
|
searchParams: {
|
||||||
const [activePage, setActivePage] = useState<number>(1);
|
["страница-тендеров"]: string;
|
||||||
const [ordering, setOrdering] = useState<number>(1);
|
};
|
||||||
|
}
|
||||||
|
const TendersList: React.FC<Props> = ({ searchParams }) => {
|
||||||
|
const [activePage, setActivePage] = useState<number>(
|
||||||
|
+searchParams["страница-тендеров"] || 1
|
||||||
|
);
|
||||||
const { data: tenders, getTenders, isLoading, error } = useTendersStore();
|
const { data: tenders, getTenders, isLoading, error } = useTendersStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getTenders(ordering);
|
getTenders(activePage);
|
||||||
}, []);
|
}, [activePage]);
|
||||||
console.log(tenders);
|
|
||||||
|
const handleSubmit = () => {};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<SearchForm />
|
<SearchForm
|
||||||
|
placeholder="Введите ключевое слово"
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="flex flex-col gap-2 pb-[96px] items-center">
|
<div className="flex flex-col gap-2 pb-[96px] items-center">
|
||||||
{tenders?.results?.map((tender) => (
|
{tenders?.results?.map((tender) => (
|
||||||
@ -42,7 +50,7 @@ const TendersList = () => {
|
|||||||
next={tenders.next}
|
next={tenders.next}
|
||||||
count={tenders.count as number}
|
count={tenders.count as number}
|
||||||
current_count={tenders.results.length}
|
current_count={tenders.results.length}
|
||||||
limit={20}
|
limit={10}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -22,7 +22,9 @@ export const useTendersStore = create<useTendersStore>((set) => ({
|
|||||||
try {
|
try {
|
||||||
set({ isLoading: true });
|
set({ isLoading: true });
|
||||||
|
|
||||||
const res = await apiInstance.get(`/procurements/?ordering=${ordering}`);
|
const res = await apiInstance.get(
|
||||||
|
`/procurements/?ordering=${ordering}/?limit=${10}/`
|
||||||
|
);
|
||||||
set({ data: res.data });
|
set({ data: res.data });
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (error instanceof AxiosError) {
|
if (error instanceof AxiosError) {
|
||||||
|
Loading…
Reference in New Issue
Block a user