detail page updated , add subcategory zustand

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-04-17 11:04:34 +05:00
parent 83bfb6296d
commit ebc08eff8f
7 changed files with 173 additions and 65 deletions

View File

@@ -1,98 +1,134 @@
"use client"; "use client";
import { useCarDetail } from "@/components/lib_components/carDetailProvider";
import Text from "@/components/lib_components/text"; import Text from "@/components/lib_components/text";
import Image from "next/image"; import Image from "next/image";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import CarRentalModal from "@/components/lib_components/carRentalModal"; import CarRentalModal from "@/components/lib_components/carRentalModal";
import { useTranslation } from "react-i18next"; import { usePathname, useParams } from "next/navigation";
import { useCarType } from "@/store/carType";
import { usePathname } from "next/navigation";
import { logoImg } from "@/assets"; import { logoImg } from "@/assets";
import { useSubCategory } from "@/store/subCategory";
import { minimumValues } from "@/data/minimimValues";
import { LoadingSkeleton } from "@/components/loadingProduct";
import { EmptyState } from "@/components/emptyState";
import { ErrorState } from "@/components/errorState";
const baseUrl = "https://api.spes-texnika.uz/api/v1/products/"; const baseUrl = "https://api.spes-texnika.uz/api/v1/products/";
type Lang = "uz" | "ru" | "en";
const validLangs: Lang[] = ["uz", "ru", "en"];
function checkCategory(categoryName: string | undefined, lang: string) {
if (!categoryName || !validLangs.includes(lang as Lang)) {
return { isMinimum: false, text: "" };
}
const validLang = lang as Lang;
const matched = minimumValues[validLang].find(
(item: string) => item === categoryName,
);
if (matched)
return { isMinimum: true, text: minimumValues.values[validLang] };
return { isMinimum: false, text: "" };
}
export default function CarDetailPage() { export default function CarDetailPage() {
const [modalOpen, setModalOpen] = useState<boolean>(false); const [modalOpen, setModalOpen] = useState<boolean>(false);
const { t } = useTranslation();
// tools of request const initialSubCategory = useSubCategory(
const initialCar = useCarType((state) => state.initialCar); (state) => state.initialSubCategory,
);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const pathname = usePathname(); const pathname = usePathname();
const params = useParams();
const lang = pathname.split("/")[1]; const lang = pathname.split("/")[1];
const carId = params.carDeatil as string;
const [cars, setCars] = useState<any[]>([]); const [cars, setCars] = useState<any[]>([]);
console.log("car type id: ", initialCar.id); const fetchProducts = async () => {
console.log("request URL: ", `${baseUrl}${initialCar.id}/`); if (!carId) {
useEffect(() => {
// Agar ID bo'lmasa, fetchni ishga tushirma
if (!initialCar.id) {
setLoading(false); setLoading(false);
return; return;
} }
const fetchProducts = async () => { try {
try { setLoading(true);
setLoading(true); setError(null);
setError(null);
const response = await fetch(`${baseUrl}${initialCar.id}/`, { const response = await fetch(`${baseUrl}${carId}/`, {
headers: { headers: {
"Accept-Language": lang, "Accept-Language": lang,
}, },
}); });
if (!response.ok) { if (!response.ok) {
throw new Error("Server xatosi"); throw new Error("Server xatosi");
}
const result = await response.json();
console.log("backend full response: ", result);
console.log("backend Data: ", result?.data);
// Data array ekanligini tekshirish
if (result?.data) {
if (Array.isArray(result.data)) {
setCars(result.data);
} else {
// Agar object bo'lsa, uni array ichiga o'rab qo'yamiz
setCars([result.data]);
}
} else {
setCars([]);
}
} catch (error) {
console.log("Xatolik: ", error);
setError(error instanceof Error ? error.message : "Noma'lum xatolik");
setCars([]);
} finally {
setLoading(false);
} }
};
const result = await response.json();
console.log("backend full response: ", result);
console.log("backend Data: ", result?.data);
if (result?.data) {
if (Array.isArray(result.data)) {
setCars(result.data);
} else {
setCars([result.data]);
}
} else {
setCars([]);
}
} catch (err) {
console.log("Xatolik: ", err);
setError(err instanceof Error ? err.message : "Noma'lum xatolik");
setCars([]);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchProducts(); fetchProducts();
}, [lang]); // initialCar.id ham dependency ga qo'shildi }, [carId, lang]);
const firstData = cars ? cars[0] : undefined; if (loading) {
return (
<div className="my-10 max-w-[1200px] w-full mx-auto grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 px-2">
<LoadingSkeleton />
</div>
);
}
if (error) {
return (
<div className="my-10 max-w-[1200px] w-full mx-auto px-2">
<ErrorState message={error} onRetry={fetchProducts} />
</div>
);
}
const firstData = cars[0];
if (!firstData) { if (!firstData) {
return <>Maluot topilmadi</>; return (
<div className="my-10 max-w-[1200px] w-full mx-auto px-2">
<EmptyState />
</div>
);
} }
const { isMinimum, text } = checkCategory(initialSubCategory.name, lang);
return ( return (
<div <div
dir="ltr" dir="ltr"
className="my-10 max-w-[1200px] w-full mx-auto space-y-8 flex flex-col items-start justify-center px-2" className="my-10 max-w-[1200px] w-full mx-auto space-y-8 flex flex-col items-start justify-center px-2"
> >
{/* 1 Mashina nomi */} {/* Mashina nomi */}
<div className="text-2xl font-bold w-full text-center text-secondary mb-10"> <div className="text-2xl font-bold w-full text-center text-secondary mb-10">
<Text txt={""} /> <Text txt={""} />
</div> </div>
{/* 2 Rasmi + asosiy narx malumotlari */} {/* Rasmi + asosiy narx ma'lumotlari */}
<div className="flex flex-col md:flex-row md:items-start items-center gap-6 justify-center w-full"> <div className="flex flex-col md:flex-row md:items-start items-center gap-6 justify-center w-full">
{/* Mashina rasmi */} {/* Mashina rasmi */}
<div className="max-w-[600px] w-full h-auto"> <div className="max-w-[600px] w-full h-auto">
@@ -105,22 +141,24 @@ export default function CarDetailPage() {
/> />
</div> </div>
{/* Asosiy malumotlar */} {/* Asosiy ma'lumotlar */}
<div className="lg:space-y-6 space-y-3 w-full"> <div className="lg:space-y-6 space-y-3 w-full">
<div className="text-lg font-semibold flex gap-2"> <div className="text-lg font-semibold flex gap-2">
<Text txt="hour-price" /> {isMinimum ? <p>{text}</p> : <Text txt="hour-price" />} :
<span className="font-medium flex gap-2 text-gray-500"> <span className="font-medium flex gap-2 text-gray-500">
{firstData.price?.toLocaleString("uz-UZ")} {firstData.price?.toLocaleString("uz-UZ")}
<Text txt="wallet" /> <Text txt="wallet" />
</span> </span>
</div> </div>
<div className="text-lg font-semibold flex gap-2"> {!isMinimum && (
<Text txt="min-time" /> <div className="text-lg font-semibold flex gap-2">
<span className="font-medium flex gap-2 text-gray-500"> <Text txt="min-time" />
{firstData.minimal_order} <span className="font-medium flex gap-2 text-gray-500">
<Text txt="time" /> {firstData.minimal_order}
</span> <Text txt="time" />
</div> </span>
</div>
)}
{/* Izoh */} {/* Izoh */}
<div className="space-y-2 text-gray-500 text-lg"> <div className="space-y-2 text-gray-500 text-lg">
@@ -141,7 +179,7 @@ export default function CarDetailPage() {
</div> </div>
</div> </div>
{/* 3 Texnik xususiyatlar (faqat mavjudlari) */} {/* Texnik xususiyatlar */}
<div className="w-full border-t border-gray-300 pt-6"> <div className="w-full border-t border-gray-300 pt-6">
<h2 className="text-xl font-semibold mb-4 text-secondary"> <h2 className="text-xl font-semibold mb-4 text-secondary">
Texnik xususiyatlari Texnik xususiyatlari
@@ -159,7 +197,7 @@ export default function CarDetailPage() {
</div> </div>
</div> </div>
{/* 4 Ijara modal */} {/* Ijara modal */}
<CarRentalModal <CarRentalModal
car={firstData} car={firstData}
isOpen={modalOpen} isOpen={modalOpen}

View File

@@ -21,7 +21,7 @@ export default function InnerProductcard({ data }: { data: any }) {
return ( return (
<Link <Link
href={`/${route.lang}/${route.carType}/${data.name}`} href={`/${route.lang}/${route.carType}/${data.id}`}
onClick={() => { onClick={() => {
setDetail(data); setDetail(data);
setInitialCar(carInfo); setInitialCar(carInfo);

43
components/errorState.tsx Normal file
View File

@@ -0,0 +1,43 @@
import Text from "./lib_components/text";
interface ErrorStateProps {
message?: string;
onRetry?: () => void;
}
export const ErrorState = ({ message, onRetry }: ErrorStateProps) => {
return (
<div className="col-span-full flex flex-col items-center justify-center py-20 px-4">
<div className="w-24 h-24 mb-6 rounded-full bg-red-100 flex items-center justify-center">
<svg
className="w-12 h-12 text-red-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"
/>
</svg>
</div>
<h3 className="text-2xl font-bold text-[#0c1239] mb-2">
<Text txt="errorTitle" />
</h3>
{message && (
<p className="text-gray-500 text-center max-w-md mb-4">{message}</p>
)}
{onRetry && (
<button
onClick={onRetry}
className="bg-secondary p-3 px-6 rounded-lg border-2 border-secondary text-white
hover:cursor-pointer hover:bg-white hover:text-secondary transition-all"
>
<Text txt="retry" />
</button>
)}
</div>
);
};

10
data/minimimValues.ts Normal file
View File

@@ -0,0 +1,10 @@
export const minimumValues = {
uz: ["Shalanda va Traller", "Evakuator", "Samosval"],
ru: ["Шаланда и трейлер", "Эвакуатор", "Самосвал"],
en: ["Shalanda and Trailer", "Evakuator", "Samosval"],
values: {
uz: "1 ta reys uchun narx",
ru: "Цена за 1 рейс",
en: "Price per trip",
},
};

View File

@@ -184,6 +184,8 @@
"manipulyator": "Манипулятор", "manipulyator": "Манипулятор",
"Avtovishka": "Автовышка", "Avtovishka": "Автовышка",
"Avtolift": "Автолифт", "Avtolift": "Автолифт",
"errorTitle": "Произошла ошибка",
"retry": "Повторить",
"downloadError": "Ошибка при загрузке данных", "downloadError": "Ошибка при загрузке данных",
"noData": "Специальная техника не найдена", "noData": "Специальная техника не найдена",
"noDataDesc": "На данный момент товары отсутствуют. Пожалуйста, попробуйте позже." "noDataDesc": "На данный момент товары отсутствуют. Пожалуйста, попробуйте позже."

View File

@@ -184,6 +184,8 @@
"manipulyator": "Manipulyator", "manipulyator": "Manipulyator",
"Avtovishka": "Avtovishka", "Avtovishka": "Avtovishka",
"Avtolift": "Avtolift", "Avtolift": "Avtolift",
"errorTitle": "Xatolik yuz berdi",
"retry": "Qayta urinish",
"downloadError": "Ma'lumotlarni yuklashda xatolik", "downloadError": "Ma'lumotlarni yuklashda xatolik",
"noData": "Mahsus texnikalar topilmadi", "noData": "Mahsus texnikalar topilmadi",
"noDataDesc": "Hozircha mahsulotlar mavjud emas. Iltimos, keyinroq qayta urinib ko'ring." "noDataDesc": "Hozircha mahsulotlar mavjud emas. Iltimos, keyinroq qayta urinib ko'ring."

13
vercel.json Normal file
View File

@@ -0,0 +1,13 @@
{
"framework": "nextjs",
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" }
]
}
]
}