connected to backend

This commit is contained in:
nabijonovdavronbek619@gmail.com
2026-02-04 10:01:25 +05:00
parent 1d0698573f
commit e7b838e3fe
21 changed files with 571 additions and 395 deletions

View File

@@ -3,45 +3,85 @@
import { useCarDetail } from "@/components/lib_components/carDetailProvider";
import Text from "@/components/lib_components/text";
import Image from "next/image";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import CarRentalModal from "@/components/lib_components/carRentalModal";
import { useTranslation } from "react-i18next";
import { useCarType } from "@/store/carType";
import { usePathname } from "next/navigation";
import { logoImg } from "@/assets";
const baseUrl = "https://api.spes-texnika.uz/api/v1/products/";
export default function CarDetailPage() {
const { detail } = useCarDetail();
const [modalOpen, setModalOpen] = useState<boolean>(false);
const { t } = useTranslation();
if (!detail) {
return (
<div className="my-20 text-center text-gray-500">
<Text txt="Mahsulot topilmadi yoki hali tanlanmagan." />
</div>
);
// tools of request
const initialCar = useCarType((state) => state.initialCar);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const pathname = usePathname();
const lang = pathname.split("/")[1];
const [cars, setCars] = useState<any[]>([]);
console.log("car type id: ", initialCar.id);
console.log("request URL: ", `${baseUrl}${initialCar.id}/`);
useEffect(() => {
// Agar ID bo'lmasa, fetchni ishga tushirma
if (!initialCar.id) {
setLoading(false);
return;
}
const {t} = useTranslation();
const fetchProducts = async () => {
try {
setLoading(true);
setError(null);
const techSpecs: Record<string, string | number | undefined> = {
[t("weight_kg")]: detail.weight_kg && `${detail.weight_kg.toLocaleString("uz-UZ")} kg`,
[t("maxLength_m")]: detail.maxLength_m && `${detail.maxLength_m} m`,
[t("maxHeight_m")]: detail.maxHeight_m && `${detail.maxHeight_m} m`,
[t("capacity_tons")]: detail.capacity_tons && `${detail.capacity_tons} t`,
[t("capacity_kg")]: detail.capacity_kg && `${detail.capacity_kg} kg`,
[t("fuelType")]: detail.fuelType,
[t("tankVolume_m3")]: detail.tankVolume_m3 && `${detail.tankVolume_m3}`,
[t("maxSpeed_kmh")]: detail.maxSpeed_kmh && `${detail.maxSpeed_kmh} km/soat`,
[t("intercooler")]: detail.intercooler,
[t("enginePower_hp")]: detail.enginePower_hp,
[t("transmission")]: detail.transmission,
[t("bom")]: detail.bom && `${detail.bom} m`,
[t("qazish")]: detail.qazish && `${detail.qazish} m`,
[t("pichoq")]: detail.pichoq && `${detail.pichoq} m`,
[t("zichlash")]: detail.zichlash && `${detail.zichlash} m`,
[t("siqish")]: detail.siqish && `${detail.siqish} bar`,
[t("havo")]: detail.havo && `${detail.havo} l`,
[t("kompressor_sig")]: detail.kompressor_sig && `${detail.kompressor_sig} l`
const response = await fetch(`${baseUrl}${initialCar.id}/`, {
headers: {
"Accept-Language": lang,
},
});
if (!response.ok) {
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);
}
};
fetchProducts();
}, [lang]); // initialCar.id ham dependency ga qo'shildi
const firstData = cars ? cars[0] : undefined;
if (!firstData) {
return <>Maluot topilmadi</>;
}
return (
<div
dir="ltr"
@@ -49,7 +89,7 @@ export default function CarDetailPage() {
>
{/* 1⃣ Mashina nomi */}
<div className="text-2xl font-bold w-full text-center text-secondary mb-10">
<Text txt={detail.name} />
<Text txt={""} />
</div>
{/* 2⃣ Rasmi + asosiy narx malumotlari */}
@@ -57,8 +97,8 @@ export default function CarDetailPage() {
{/* Mashina rasmi */}
<div className="max-w-[600px] w-full h-auto">
<Image
src={detail.image}
alt={detail.name}
src={firstData?.image ? firstData?.image : logoImg}
alt={firstData?.name ? firstData?.name : "image"}
width={600}
height={200}
className="rounded-lg object-cover border border-gray-200 w-full"
@@ -70,24 +110,17 @@ export default function CarDetailPage() {
<div className="text-lg font-semibold flex gap-2">
<Text txt="hour-price" />
<span className="font-medium flex gap-2 text-gray-500">
{detail.price?.toLocaleString("uz-UZ")}
{firstData.price?.toLocaleString("uz-UZ")}
<Text txt="wallet" />
</span>
</div>
<div className="text-lg font-semibold flex gap-2">
<Text txt="min-time" />
<span className="font-medium flex gap-2 text-gray-500">
{detail.min_order_time}
{firstData.minimal_order}
<Text txt="time" />
</span>
</div>
<div className="text-lg font-semibold flex gap-2">
<Text txt="day-price" />
<span className="font-medium flex gap-2 text-gray-500">
{detail.price && (detail.price * 8).toLocaleString("uz-UZ")}
<Text txt="wallet" />
</span>
</div>
{/* Izoh */}
<div className="space-y-2 text-gray-500 text-lg">
@@ -110,17 +143,17 @@ export default function CarDetailPage() {
{/* 3⃣ Texnik xususiyatlar (faqat mavjudlari) */}
<div className="w-full border-t border-gray-300 pt-6">
<h2 className="text-xl font-semibold mb-4 text-secondary">Texnik xususiyatlari</h2>
<h2 className="text-xl font-semibold mb-4 text-secondary">
Texnik xususiyatlari
</h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-gray-700">
{Object.entries(techSpecs)
.filter(([_, value]) => value !== undefined)
.map(([key, value]) => (
{firstData?.features.map((item: any) => (
<div
key={key}
key={item.id}
className="p-3 rounded-md bg-gray-50 border border-gray-200 hover:bg-gray-100 transition"
>
<p className="font-medium">{key}:</p>
<p className="text-gray-600">{value}</p>
<p className="font-medium">{item?.name}:</p>
<p className="text-gray-600">{item?.value}</p>
</div>
))}
</div>
@@ -128,7 +161,7 @@ export default function CarDetailPage() {
{/* 4⃣ Ijara modal */}
<CarRentalModal
car={detail}
car={firstData}
isOpen={modalOpen}
onClose={() => setModalOpen(false)}
/>

View File

@@ -1,194 +1,100 @@
"use client";
import InnerProductcard from "@/components/cards/innerProductcard";
import { EmptyState } from "@/components/emptyState";
import Text from "@/components/lib_components/text";
import Title from "@/components/lib_components/title";
import {
asfalt,
assenizator,
avtogreyderlar,
avtokran,
avtolift,
avtovishka,
betonNasoslar,
buldozerlar,
cement_trucks,
dumb_trucks,
eks_yuklagichlar,
evakuatorDemo,
forkliftlar,
gildirakli_eks,
katkalar,
manipulyator,
mini_eks,
mini_loaders,
minora,
old_yuklagichlar,
paletli_eks,
shalandaTraller,
teleskop_yuklagichlar,
trailers,
vodovoz,
} from "@/data";
import { useParams } from "next/navigation";
import { LoadingSkeleton } from "@/components/loadingProduct";
import { useCarType } from "@/store/carType";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
const baseUrl = "https://api.spes-texnika.uz/api/v1/subcategory/";
export default function CartType() {
const router = useParams();
const carType = router.carType;
const initialCar = useCarType((state) => state.initialCar);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const pathname = usePathname();
const lang = pathname.split("/")[1];
const [cars, setCars] = useState<any[]>([]);
console.log("car type id: ", initialCar.id);
console.log("request URL: ", `${baseUrl}${initialCar.id}/`);
useEffect(() => {
// Agar ID bo'lmasa, fetchni ishga tushirma
if (!initialCar.id) {
setLoading(false);
return;
}
const fetchProducts = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(`${baseUrl}${initialCar.id}/`, {
headers: {
"Accept-Language": lang,
},
});
if (!response.ok) {
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);
}
};
fetchProducts();
}, [lang]); // initialCar.id ham dependency ga qo'shildi
return (
<div dir="ltr" className="my-20">
<Title text="tex-rent" />
{/* Error message */}
{error && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-600 text-center">
<Text txt="downloadError" />: {error}
</p>
</div>
)}
{/* car type groups */}
<div className="mt-10 grid lg:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5 max-w-[1200px] w-full mx-auto">
{/* Avtosementavoz */}
{carType === "cement-truck" &&
cement_trucks.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Samasvallar */}
{carType === "dumb-truck" &&
dumb_trucks.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Traylerlar */}
{carType === "trailers" &&
trailers.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Avtoliftlar */}
{carType === "Avtolift" &&
avtolift.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Avtovishka */}
{carType === "Avtovishka" &&
avtovishka.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Avtokran */}
{carType === "avtocranes" &&
avtokran.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Minora kranlar */}
{carType === "tower-cranes" &&
minora.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Minora kranlar */}
{carType === "mini-loaders" &&
mini_loaders.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* old yuklagichlar */}
{carType === "front-loaders" &&
old_yuklagichlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Teleskopik yuklagichlar */}
{carType === "tele-loaders" &&
teleskop_yuklagichlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* forkliftlar */}
{carType === "forklift-trucks" &&
forkliftlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Paletli ekskavatorlar */}
{carType === "crawler-excavators" &&
paletli_eks.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* G'ildirakli ekskavatorlar */}
{carType === "wheel-excavators" &&
gildirakli_eks.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Mini-ekskavatorlar */}
{carType === "mini-excavators" &&
mini_eks.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* ekskavator yuklagichlar */}
{carType === "excavator-loaders" &&
eks_yuklagichlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* avtogreyderlar */}
{carType === "avtograders" &&
avtogreyderlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Buldozerlar */}
{carType === "buldozers" &&
buldozerlar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Katkalar */}
{carType === "katkas" &&
katkalar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* Evakuatorlar */}
{carType === "evakuator" &&
evakuatorDemo.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* shalanda */}
{carType === "shalanda_traller" &&
shalandaTraller.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* asfalt yotqizuvchi */}
{carType === "asfalt_frez" &&
asfalt.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* asfalt ko'chiruvchi */}
{carType === "beton_nasos" &&
betonNasoslar.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* manipulyator */}
{carType === "manipulyator" &&
manipulyator.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* vodovoz */}
{carType === "vodovoz" &&
vodovoz.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
{/* assertizator */}
{carType === "assenizator" &&
assenizator.map((item) => {
return <InnerProductcard data={item} key={item.id} />;
})}
<div className="mt-10 grid lg:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-5 max-w-[1200px] w-full mx-auto px-4">
{loading ? (
<LoadingSkeleton />
) : cars.length > 0 ? (
// MUHIM: Array ustidan map qilish kerak!
cars.map((car, index) => (
<InnerProductcard key={car.id || index} data={car} />
))
) : (
<EmptyState />
)}
</div>
</div>
);

View File

@@ -1,4 +1,4 @@
import Products from "@/components/pageParts/products";
import Products from "@/components/pageParts/products/products";
import Texnika from "@/components/pageParts/texnika";
import Offer from "@/components/pageParts/offer";
import Faq from "@/components/pageParts/faq";
@@ -27,4 +27,3 @@ export default function Home() {
</div>
);
}

View File

@@ -5,9 +5,11 @@ import Image from "next/image";
import { useParams } from "next/navigation";
import Text from "../lib_components/text";
import Link from "next/link";
import { useCarType } from "@/store/carType";
export default function CarType_Header() {
const params = useParams();
const initialCar = useCarType((state) => state.initialCar);
console.log(params);
return (
<div
@@ -47,12 +49,20 @@ export default function CarType_Header() {
<Text txt="home" />
</Link>
/
<Link href={`/${params.lang}/${params.carType}`} className={params.carDeatil ? "hover:text-secondary hover:cursor-pointer":"text-secondary"}>
<Text txt={`${params.carType}`} />
<Link
href={`/${params.lang}/${params.carType}`}
className={
params.carDeatil
? "hover:text-secondary hover:cursor-pointer"
: "text-secondary"
}
>
{initialCar.name}
</Link>
{params.carDeatil && '/'}
{params.carDeatil && <div className="text-secondary">
<Text txt={`${params.carType}`} /></div>}
{params.carDeatil && "/"}
{params.carDeatil && (
<div className="text-secondary">{initialCar.name}</div>
)}
</div>
</div>
</div>

View File

@@ -1,5 +1,4 @@
"use client";
import { innerCardTypes } from "@/types";
import Link from "next/link";
import { useParams } from "next/navigation";
@@ -8,74 +7,132 @@ import Image from "next/image";
import Text from "../lib_components/text";
import { useCarDetail } from "../lib_components/carDetailProvider";
import { motion } from "framer-motion";
import { useCarType } from "@/store/carType";
export default function InnerProductcard({ data }: { data: innerCardTypes }) {
export default function InnerProductcard({ data }: { data: any }) {
const route = useParams();
const { setDetail } = useCarDetail();
const setInitialCar = useCarType((state) => state.setInitialCar);
const carInfo = {
id: data?.id,
name: data?.name,
};
return (
<Link
href={`/${route.lang}/${route.carType}/${data.name}`}
onClick={() => {
setDetail(data);
setInitialCar(carInfo);
}}
className="block h-full"
>
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, amount: 0.2 }}
transition={{ duration: 0.5, ease: "easeOut" }}
whileHover={{
scale: 1.05,
boxShadow: "0px 0px 15px rgba(0,0,0,0.1)",
}}
whileTap={{ scale: 0.97 }}
className="h-[420px] rounded-lg overflow-hidden bg-white transition-all"
className="h-full group"
>
<Link
href={`/${route.lang}/${route.carType}/${data.name}`}
onClick={() => setDetail(data)}
className="h-full flex flex-col items-center justify-between rounded-lg hover:cursor-pointer"
<div
className="h-full bg-white rounded-2xl overflow-hidden border-2 border-gray-100
hover:border-[#f2a01c] transition-all duration-300
shadow-sm hover:shadow-xl flex flex-col"
>
{/* Rasm container - katta va responsive */}
<div
className="relative w-full aspect-[4/3] bg-gradient-to-br from-gray-50 via-white to-gray-50
overflow-hidden group-hover:bg-gradient-to-br group-hover:from-orange-50
group-hover:via-white group-hover:to-orange-50 transition-all duration-500"
>
{/* Rasm */}
<div className="absolute inset-0 p-6 flex items-center justify-center">
<motion.div
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.4 }}
className="relative w-full h-full"
>
{/* Rasm qismi */}
<div className="rounded-t-lg bg-white">
<Image
src={data.image}
alt={data.name}
width={0}
height={200}
className="object-fill w-full max-h-[200px] h-full rounded-t-sm"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-contain"
priority
/>
</div>
{/* Pastki qism */}
<div className="bg-[#fafafa] w-full p-2 px-4 rounded-b-lg flex flex-col items-start justify-start gap-2">
<div className="text-xl font-semibold">
<Text txt={data.name} />
</div>
<div className="flex gap-2">
<Text txt="hour-price" />
{data.price?.toLocaleString("uz-UZ")}
<Text txt="wallet" />
</div>
<div className="flex gap-2">
<Text txt="min-time" />
{data.min_order_time}
<Text txt="time" />
</div>
{/* Tugma animatsiyasi */}
<motion.button
whileHover={{
scale: 1.05,
backgroundColor: "#ffffff",
color: "#dc2626", // hoverda text-secondary (agar sizda secondary = red)
borderColor: "#dc2626",
}}
whileTap={{ scale: 0.95 }}
transition={{ duration: 0.3 }}
className="w-full p-3 bg-secondary rounded-lg text-white border-2 border-secondary"
>
<Text txt="more" />
</motion.button>
</div>
</Link>
</motion.div>
</div>
{/* Dekorativ element */}
<div
className="absolute top-4 right-4 w-12 h-12 bg-[#f2a01c]/10 rounded-full
flex items-center justify-center opacity-0 group-hover:opacity-100
transition-opacity duration-300"
>
<svg
className="w-6 h-6 text-[#f2a01c]"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</div>
</div>
{/* Content qismi */}
<div className="flex-1 p-6 flex flex-col justify-between bg-white">
{/* Nomi va tavsif */}
<div className="mb-4">
<h3
className="text-xl font-bold text-[#0c1239] mb-2 line-clamp-2
group-hover:text-[#f2a01c] transition-colors duration-300"
>
<Text txt={data.name} />
</h3>
{/* Agar qo'shimcha ma'lumot bo'lsa */}
{data.description && (
<p className="text-sm text-gray-600 line-clamp-2">
<Text txt={data.description} />
</p>
)}
</div>
{/* Tugma - past qismda */}
<div className="pt-4 border-t border-gray-100">
<motion.div
whileHover={{ x: 4 }}
transition={{ duration: 0.2 }}
className="flex items-center justify-between text-[#f2a01c] font-semibold"
>
<span>
<Text txt="more" />
</span>
<svg
className="w-5 h-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 5l7 7-7 7"
/>
</svg>
</motion.div>
</div>
</div>
</div>
</motion.div>
</Link>
);
}

View File

@@ -1,15 +1,23 @@
"use client";
import { ProductTypes } from "@/types";
import Image from "next/image";
import Link from "next/link";
import Text from "../lib_components/text";
import { useParams } from "next/navigation";
import { motion } from "framer-motion";
import { useCarType } from "@/store/carType";
export default function ProductCard({ data }: { data: ProductTypes }) {
export default function ProductCard({ data }: { data: any }) {
const { lang } = useParams();
const setInitialCar = useCarType((state) => state.setInitialCar);
const carData = {
name: data?.name,
id: data?.id,
};
console.log("data: ", data);
return (
<motion.div
initial={{ opacity: 0, y: 50 }}
@@ -23,14 +31,15 @@ export default function ProductCard({ data }: { data: ProductTypes }) {
className="rounded-xl border-2 border-primary h-[430px]"
>
<Link
href={`/${lang}/${data.path}`}
href={`/${lang}/${data.name}`}
onClick={() => setInitialCar(carData)}
className="h-full flex flex-col items-center justify-between rounded-lg bg-white transition-transform"
>
{/* Yuqori qism - rasm */}
<div className="rounded-t-lg bg-white py-10 px-2 flex justify-center items-center">
<Image
src={data.image}
alt={data.truck_name}
alt={data.name}
width={260}
height={200}
className="object-contain max-h-[200px] h-full rounded-xl"
@@ -40,10 +49,10 @@ export default function ProductCard({ data }: { data: ProductTypes }) {
{/* Pastki qism - matn */}
<div className="bg-[#fafafa] w-full py-5 rounded-b-lg flex flex-col items-center justify-center space-y-1">
<div className="font-medium text-primary text-xl text-center">
<Text txt={data.truck_name} />
<Text txt={data.name} />
</div>
<div className="text-secondary text-md font-extrabold text-center">
<Text txt={data.desc} />
<Text txt="agreement" />
</div>
<div className="text-center text-secondary bg-primary max-w-[200px] w-full rounded-xl text-lg py-2 hover:cursor-pointer">
<Text txt="more" />

30
components/emptyState.tsx Normal file
View File

@@ -0,0 +1,30 @@
import Text from "./lib_components/text";
// Empty State Component
export const EmptyState = () => {
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-[#f2a01c]/10 flex items-center justify-center">
<svg
className="w-12 h-12 text-[#f2a01c]"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4"
/>
</svg>
</div>
<h3 className="text-2xl font-bold text-[#0c1239] mb-2">
<Text txt="noData" />
</h3>
<p className="text-gray-600 text-center max-w-md">
<Text txt="noDataDesc" />
</p>
</div>
);
};

View File

@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
export default function Time() {
const {t} = useTranslation();
return (
<div dir='ltr' className='icon_animation2 fixed bottom-10 right-20 z-10 rounded-[50%] bg-primary p-5 text-lg text-secondary flex flex-col items-center justify-center'>
<div dir='ltr' className='icon_animation2 fixed bottom-10 right-20 z-40 rounded-[50%] bg-primary p-5 text-lg text-secondary flex flex-col items-center justify-center'>
<p>{t('work_day_title')}</p>
<p>24/7</p>
</div>

View File

@@ -37,7 +37,7 @@ export default function UpScrollIcon() {
{showButton && (
<span
onClick={scrollToTop}
className="fixed bottom-6 right-6 bg-secondary hover:bg-primary text-white p-3 rounded-full cursor-pointer shadow-lg transition"
className="fixed z-40 bottom-6 right-6 bg-secondary hover:bg-primary text-white p-3 rounded-full cursor-pointer shadow-lg transition"
>
<FaArrowUp size={20} />
</span>

View File

@@ -0,0 +1,19 @@
// Loading Skeleton Component
export const LoadingSkeleton = () => {
return (
<>
{[1, 2, 3, 4].map((item) => (
<div
key={item}
className="bg-white rounded-lg shadow-md overflow-hidden animate-pulse"
>
<div className="w-full h-48 bg-gray-200"></div>
<div className="p-4">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
</div>
))}
</>
);
};

View File

@@ -48,7 +48,7 @@ export default function HeroSection() {
className={`${navigationPrevEl.replace(
".",
""
)} w-10 h-10 absolute z-10 left-[10%] top-50 p-0 bg-primary text-[30px] text-center text-white flex items-center justify-center hover:bg-secondary hover:cursor-pointer transition`}
)} w-10 h-10 absolute z-10 left-[10%] top-50 p-0 bg-primary text-[30px] text-center text-white lg:flex hidden items-center justify-center hover:bg-secondary hover:cursor-pointer transition`}
>
<ArrowLeft />
</button>
@@ -56,7 +56,7 @@ export default function HeroSection() {
className={`${navigationNextEl.replace(
".",
""
)} w-10 h-10 absolute z-10 right-[10%] top-50 bg-primary text-[30px] text-center text-white flex items-center justify-center hover:bg-secondary hover:cursor-pointer transition `}
)} w-10 h-10 absolute z-10 right-[10%] top-50 bg-primary text-[30px] text-center text-white lg:flex hidden items-center justify-center hover:bg-secondary hover:cursor-pointer transition `}
>
<ArrowRight />
</button>

View File

@@ -14,7 +14,7 @@ export default function Map() {
</div>
{/* contact information */}
<div className="absolute flex flex-col gap-3 sm:top-20 top-5 sm:right-20 z-50 bg-white rounded-[15px] p-5 px-10 max-w-[400px] w-full text-left ">
<div className="absolute flex flex-col gap-3 sm:top-20 top-5 sm:right-20 z-30 bg-white rounded-[15px] p-5 px-10 max-w-[400px] w-full text-left ">
<div className="text-left flex w-full justify-start">
<Title text="contacts" />
</div>

View File

@@ -1,63 +0,0 @@
"use client";
import React, { useEffect, useState } from "react";
import Title from "../lib_components/title";
import Text from "../lib_components/text";
import { Asphalt, Ekskavator, Forklift, Kran, Truck } from "@/assets";
import type { productFilterTypes, ProductTypes } from "@/types";
import {
allProducts,
} from "@/data";
import ProductCard from "../cards/productCard";
export default function Products() {
const [cars, setCars] = useState(allProducts);
return (
<div dir="ltr" className="max-w-[1200px] w-full mx-auto">
{/* title part */}
<div className="flex flex-col mb-10">
<div className="flex items-center justify-center w-full ">
<div className="text-secondary px-2 text-[18px] font-semibold ">
<Text txt="katalog" />
</div>
</div>
<Title text="pricing-h2" />
</div>
{/* product filters */}
{/* <div className="flex flex-wrap gap-1 gap-y-4 items-center justify-center mb-10 ">
{productFilterTypesMainPage.map((item, index) => (
<button
key={index}
onClick={() => setProductFilter(item.name)}
className={`${
productFilter === item.name ? "bg-secondary" : ""
} flex items-center gap-2 h-[58px] hover:bg-secondary border-gray-300 hover:border-secondary border-[1px] px-7 text-2xl rounded-tr-full rounded-bl-full `}
>
<Text txt={item.name} />
{item.image && (
<Image
src={item.image}
alt="Truck images"
width={50}
height={50}
className="object-cover"
/>
)}
</button>
))}
</div> */}
{/* products */}
<div className="px-4 grid gap-5 grid-cols-1 place-content-center min-[500px]:grid-cols-2 min-lg:grid-cols-4 min-[1210px]:grid-cols-4">
{cars.map((item: ProductTypes, index: number) => (
<div key={index}>
<ProductCard data={item} />
</div>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,95 @@
"use client";
import React, { useEffect, useState } from "react";
import Title from "../../lib_components/title";
import Text from "../../lib_components/text";
import type { ProductTypes } from "@/types";
import { allProducts } from "@/data";
import ProductCard from "../../cards/productCard";
import { LoadingSkeleton } from "@/components/loadingProduct";
import { EmptyState } from "@/components/emptyState";
import { usePathname } from "next/navigation";
import { baseUrl } from "@/data/url";
export default function Products() {
const [cars, setCars] = useState<ProductTypes[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const pathname = usePathname();
const lang = pathname.split("/")[1];
useEffect(() => {
const fetchProducts = async () => {
try {
setLoading(true);
setError(null);
const response = await fetch(baseUrl, {
headers: {
"Accept-Language": lang,
},
});
if (!response.ok) {
throw new Error("Server xatosi");
}
const data = await response.json();
console.log("backend Data: ", data?.data);
if (data?.data && data.data.length > 0) {
setCars(data.data);
} else {
setCars([]);
}
} catch (error) {
console.log("Xatolik: ", error);
setError(error instanceof Error ? error.message : "Noma'lum xatolik");
// Xatolik bo'lsa ham local data'ni ko'rsatish
setCars(allProducts);
} finally {
setLoading(false);
}
};
fetchProducts();
}, [lang]); // Bo'sh array - faqat bitta marta ishlaydi
return (
<div dir="ltr" className="max-w-[1200px] w-full mx-auto">
{/* title part */}
<div className="flex flex-col mb-10">
<div className="flex items-center justify-center w-full">
<div className="text-[#f2a01c] px-2 text-[18px] font-semibold">
<Text txt="katalog" />
</div>
</div>
<Title text="pricing-h2" />
</div>
{/* Error message */}
{error && (
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-600 text-center">
<Text txt="downloadError" /> : {error}
</p>
</div>
)}
{/* products */}
<div className="px-4 grid gap-5 grid-cols-1 place-content-center min-[500px]:grid-cols-2 min-lg:grid-cols-4 min-[1210px]:grid-cols-4">
{loading ? (
<LoadingSkeleton />
) : cars.length > 0 ? (
cars.map((item: ProductTypes, index: number) => (
<div key={item.id || index}>
<ProductCard data={item} />
</div>
))
) : (
<EmptyState />
)}
</div>
</div>
);
}

1
data/url.ts Normal file
View File

@@ -0,0 +1 @@
export const baseUrl = "https://api.spes-texnika.uz/api/v1/category/";

View File

@@ -1,6 +1,22 @@
// next.config.ts
const nextConfig = {
reactStrictMode: true,
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'api.spes-texnika.uz',
port: '',
pathname: '/resources/media/**',
},
{
protocol: 'https',
hostname: 'api.spes-texnika.uz',
port: '',
pathname: '/resources/media/**',
},
],
},
};
export default nextConfig;

32
package-lock.json generated
View File

@@ -25,7 +25,8 @@
"react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-scroll": "^1.9.3",
"swiper": "^12.0.3"
"swiper": "^12.0.3",
"zustand": "^5.0.11"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.14",
@@ -3583,6 +3584,35 @@
"engines": {
"node": ">=18"
}
},
"node_modules/zustand": {
"version": "5.0.11",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz",
"integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

View File

@@ -25,7 +25,8 @@
"react-icons": "^5.5.0",
"react-leaflet": "^5.0.0",
"react-scroll": "^1.9.3",
"swiper": "^12.0.3"
"swiper": "^12.0.3",
"zustand": "^5.0.11"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.1.14",

View File

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

View File

@@ -47,7 +47,7 @@
"buldozer": "Buldozer",
"katkas": "Katkalar",
"katka": "Katka",
"mini-loaders":"Kichik yuklagichlar",
"mini-loaders": "Kichik yuklagichlar",
"compressors": "Kompressorlar",
"testimonials": "Sharhlar",
"clients'": "Mijozlar",
@@ -142,7 +142,7 @@
"bom-length": "Bomning uzunligi",
"header-location": "O'zbekiston, Toshkent",
"phone": "Telefon nomer",
"news-title1": "Keyingi loyihangiz uchun eng yaxshi texnikalar ijarasi",
"news-title1": "Keyingi loyihangiz uchun eng yaxshi texnikalar ijarasi",
"news-title2": "Yangi yuklagich siz uchun eng yaxshi texnika!",
"news-title3": "Yangi kompressorlar to'plami aynan siz uchun",
"news-title4": "Bizning kuchli kranlarimiz bilan ishingiz yanada osonlashadi.",
@@ -178,9 +178,12 @@
"asfalt_yotqizuvchi": "Asfalt yotqizuvchi mashina",
"asfalt_kochiruvchi": "Asfalt kochiruvchi mashina",
"beton_nasos": "Beton nasos mashinalari",
"vodovoz":"Vodovoz",
"assenizator":"Assenizator",
"manipulyator":"Manipulyator",
"Avtovishka":"Avtovishka",
"Avtolift":"Avtolift"
"vodovoz": "Vodovoz",
"assenizator": "Assenizator",
"manipulyator": "Manipulyator",
"Avtovishka": "Avtovishka",
"Avtolift": "Avtolift",
"downloadError": "Ma'lumotlarni yuklashda xatolik",
"noData": "Mahsus texnikalar topilmadi",
"noDataDesc": "Hozircha mahsulotlar mavjud emas. Iltimos, keyinroq qayta urinib ko'ring."
}

27
store/carType.ts Normal file
View File

@@ -0,0 +1,27 @@
import { create } from "zustand";
// Type definition
interface CarType {
name: string;
id: number;
}
interface CarStore {
initialCar: CarType;
setInitialCar: (data: CarType) => void;
clearCar: () => void; // Tozalash uchun
}
export const useCarType = create<CarStore>((set) => ({
initialCar: {
name: "",
id: 0,
},
setInitialCar: (data) => set({ initialCar: data }),
clearCar: () =>
set({
initialCar: { name: "", id: 0 },
}),
}));