api ulandi

This commit is contained in:
Samandar Turgunboyev
2025-10-25 18:42:01 +05:00
parent 1a08775451
commit 05b752daf2
84 changed files with 11179 additions and 3724 deletions

View File

@@ -1,458 +1,241 @@
"use client";
import {
hotelBadge,
hotelFeature,
hotelFeatureType,
hotelTarif,
hotelTransport,
hotelType,
} from "@/pages/tours/lib/api";
import BadgeTable from "@/pages/tours/ui/BadgeTable";
import FeaturesTable from "@/pages/tours/ui/FeaturesTable";
import FeaturesTableType from "@/pages/tours/ui/FeaturesTableType";
import MealTable from "@/pages/tours/ui/MealTable";
import TarifTable from "@/pages/tours/ui/TarifTable";
import TransportTable from "@/pages/tours/ui/TransportTable";
import { Button } from "@/shared/ui/button";
import { Card, CardContent } from "@/shared/ui/card";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/shared/ui/dialog";
import { Input } from "@/shared/ui/input";
import { Label } from "@/shared/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/shared/ui/select";
import { Tabs, TabsList, TabsTrigger } from "@/shared/ui/tabs";
import { Textarea } from "@/shared/ui/textarea";
import { Edit2, Plus, Search, Trash2 } from "lucide-react";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/shared/ui/tabs";
import { useQuery } from "@tanstack/react-query";
import { AlertTriangle, Loader2 } from "lucide-react";
import React, { useState } from "react";
interface Badge {
id: number;
name: string;
color: string;
}
interface Tariff {
id: number;
name: string;
price: number;
}
interface Transport {
id: number;
name: string;
price: number;
}
interface MealPlan {
id: number;
name: string;
}
interface HotelType {
id: number;
name: string;
}
type TabId = "badges" | "tariffs" | "transports" | "mealPlans" | "hotelTypes";
type DataItem = Badge | Tariff | Transport | MealPlan | HotelType;
interface FormField {
name: string;
label: string;
type: "text" | "number" | "color" | "textarea" | "select";
required: boolean;
options?: string[];
min?: number;
max?: number;
}
interface Tab {
id: TabId;
label: string;
}
import { useTranslation } from "react-i18next";
import { useNavigate, useSearchParams } from "react-router-dom";
const ToursSetting: React.FC = () => {
const [activeTab, setActiveTab] = useState<TabId>("badges");
const [searchTerm, setSearchTerm] = useState<string>("");
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [modalMode, setModalMode] = useState<"add" | "edit">("add");
const [currentItem, setCurrentItem] = useState<DataItem | null>(null);
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const [activeTab, setActiveTab] = useState("badge");
const [featureId, setFeatureId] = useState<number | null>(null);
const navigate = useNavigate();
const [badges, setBadges] = useState<Badge[]>([
{ id: 1, name: "Bestseller", color: "#FFD700" },
{ id: 2, name: "Yangi", color: "#4CAF50" },
]);
const page = parseInt(searchParams.get("page") || "1", 10);
const pageSize = parseInt(searchParams.get("pageSize") || "10", 10);
const [tariffs, setTariffs] = useState<Tariff[]>([
{ id: 1, name: "Standart", price: 500 },
{ id: 2, name: "Premium", price: 1000 },
]);
const { data, isLoading, isError, refetch } = useQuery({
queryKey: ["all_badge", page, pageSize],
queryFn: () => hotelBadge({ page, page_size: pageSize }),
select: (res) => res.data.data,
});
const [transports, setTransports] = useState<Transport[]>([
{ id: 1, name: "Avtobus", price: 200 },
{ id: 2, name: "Minivan", price: 500 },
]);
const pageTarif = parseInt(searchParams.get("pageTarif") || "1", 10);
const pageSizeTarif = parseInt(searchParams.get("pageTarifSize") || "10", 10);
const [mealPlans, setMealPlans] = useState<MealPlan[]>([
{ id: 1, name: "BB (Bed & Breakfast)" },
{ id: 2, name: "HB (Half Board)" },
{ id: 3, name: "FB (Full Board)" },
]);
const {
data: tarifData,
isLoading: tarifLoad,
isError: tarifError,
refetch: tarifRef,
} = useQuery({
queryKey: ["all_tarif", pageTarif, pageSizeTarif],
queryFn: () => hotelTarif({ page: pageTarif, page_size: pageSizeTarif }),
select: (res) => res.data.data,
});
const [hotelTypes, setHotelTypes] = useState<HotelType[]>([
{ id: 1, name: "3 Yulduz" },
{ id: 2, name: "5 Yulduz" },
]);
const [formData, setFormData] = useState<Partial<DataItem>>({});
const getCurrentData = (): DataItem[] => {
switch (activeTab) {
case "badges":
return badges;
case "tariffs":
return tariffs;
case "transports":
return transports;
case "mealPlans":
return mealPlans;
case "hotelTypes":
return hotelTypes;
default:
return [];
}
};
const getSetterFunction = (): React.Dispatch<
React.SetStateAction<DataItem[]>
> => {
switch (activeTab) {
case "badges":
return setBadges as React.Dispatch<React.SetStateAction<DataItem[]>>;
case "tariffs":
return setTariffs as React.Dispatch<React.SetStateAction<DataItem[]>>;
case "transports":
return setTransports as React.Dispatch<
React.SetStateAction<DataItem[]>
>;
case "mealPlans":
return setMealPlans as React.Dispatch<React.SetStateAction<DataItem[]>>;
case "hotelTypes":
return setHotelTypes as React.Dispatch<
React.SetStateAction<DataItem[]>
>;
default:
return (() => {}) as React.Dispatch<React.SetStateAction<DataItem[]>>;
}
};
const filteredData = getCurrentData().filter((item) =>
Object.values(item).some((val) =>
val?.toString().toLowerCase().includes(searchTerm.toLowerCase()),
),
const pageTransport = parseInt(searchParams.get("pageTransport") || "1", 10);
const pageSizeTransport = parseInt(
searchParams.get("pageTransportSize") || "10",
10,
);
const getFormFields = (): FormField[] => {
switch (activeTab) {
case "badges":
return [
{ name: "name", label: "Nomi", type: "text", required: true },
{ name: "color", label: "Rang", type: "color", required: true },
];
case "tariffs":
return [
{ name: "name", label: "Tarif nomi", type: "text", required: true },
{ name: "price", label: "Narx", type: "number", required: true },
];
case "transports":
return [
{
name: "name",
label: "Transport nomi",
type: "text",
required: true,
},
{ name: "capacity", label: "Sig'im", type: "number", required: true },
];
case "mealPlans":
return [{ name: "name", label: "Nomi", type: "text", required: true }];
case "hotelTypes":
return [
{ name: "name", label: "Tur nomi", type: "text", required: true },
];
default:
return [];
}
};
const {
data: transportData,
isLoading: transportLoad,
isError: transportError,
refetch: transportRef,
} = useQuery({
queryKey: ["all_transport", pageTransport, pageSizeTransport],
queryFn: () =>
hotelTransport({ page: pageTransport, page_size: pageSizeTransport }),
select: (res) => res.data.data,
});
const openModal = (
mode: "add" | "edit",
item: DataItem | null = null,
): void => {
setModalMode(mode);
setCurrentItem(item);
if (mode === "edit" && item) {
setFormData(item);
} else {
setFormData({});
}
setIsModalOpen(true);
};
const pageType = parseInt(searchParams.get("pageType") || "1", 10);
const pageSizeType = parseInt(searchParams.get("pageTypeSize") || "10", 10);
const closeModal = (): void => {
setIsModalOpen(false);
setFormData({});
setCurrentItem(null);
};
const {
data: typeData,
isLoading: typeLoad,
isError: typeError,
refetch: typeRef,
} = useQuery({
queryKey: ["all_type", pageType, pageSizeType],
queryFn: () => hotelType({ page: pageType, page_size: pageSizeType }),
select: (res) => res.data.data,
});
const handleSubmit = (): void => {
const setter = getSetterFunction();
const pageFeature = parseInt(searchParams.get("pageFeature") || "1", 10);
const pageSizeFeature = parseInt(
searchParams.get("pageSizeFeature") || "10",
10,
);
if (modalMode === "add") {
const newId = Math.max(...getCurrentData().map((i) => i.id), 0) + 1;
setter([...getCurrentData(), { ...formData, id: newId } as DataItem]);
} else {
setter(
getCurrentData().map((item) =>
item.id === currentItem?.id ? { ...item, ...formData } : item,
),
);
}
closeModal();
};
const {
data: featureData,
isLoading: featureLoad,
isError: featureError,
refetch: featureRef,
} = useQuery({
queryKey: ["all_feature", pageFeature, pageSizeFeature],
queryFn: () =>
hotelFeature({ page: pageFeature, page_size: pageSizeFeature }),
select: (res) => res.data.data,
});
const handleDelete = (id: number): void => {
if (window.confirm("Rostdan ham o'chirmoqchimisiz?")) {
const setter = getSetterFunction();
setter(getCurrentData().filter((item) => item.id !== id));
}
};
const {
data: featureTypeData,
isLoading: featureTypeLoad,
isError: featureTypeError,
refetch: featureTypeRef,
} = useQuery({
queryKey: ["all_feature_type", pageFeature, pageSizeFeature, featureId],
queryFn: () =>
hotelFeatureType({
page: pageFeature,
page_size: pageSizeFeature,
feature_type: featureId!,
}),
select: (res) => res.data.data,
enabled: !!featureId,
});
const tabs: Tab[] = [
{ id: "badges", label: "Belgilar" },
{ id: "tariffs", label: "Tariflar" },
{ id: "transports", label: "Transportlar" },
{ id: "mealPlans", label: "Ovqatlanish" },
{ id: "hotelTypes", label: "Otel turlari" },
];
if (
isLoading ||
tarifLoad ||
transportLoad ||
typeLoad ||
featureLoad ||
featureTypeLoad
) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full">
<Loader2 className="w-10 h-10 animate-spin text-cyan-400" />
<p className="text-slate-400">{t("Ma'lumotlar yuklanmoqda...")}</p>
</div>
);
}
const getFieldValue = (fieldName: string): string | number => {
return (formData as Record<string, string | number>)[fieldName] || "";
if (
isError ||
tarifError ||
transportError ||
typeError ||
featureError ||
featureTypeError
) {
return (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 w-full text-center text-white gap-4">
<AlertTriangle className="w-10 h-10 text-red-500" />
<p className="text-lg">
{t("Ma'lumotlarni yuklashda xatolik yuz berdi.")}
</p>
<Button
onClick={() => {
refetch();
tarifRef();
transportRef();
typeRef();
featureRef();
featureTypeRef();
}}
className="bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg px-5 py-2 hover:opacity-90"
>
{t("Qayta urinish")}
</Button>
</div>
);
}
const handleTabChange = (value: string) => {
setActiveTab(value);
navigate({
pathname: window.location.pathname,
search: "",
});
};
return (
<div className="min-h-screen bg-gray-900 p-6 w-full">
<div className="max-w-7xl mx-auto space-y-6">
<h1 className="text-3xl font-bold">Tur Sozlamalari</h1>
<div className="min-h-screen bg-gray-900 p-6 w-full text-white">
<div className="max-w-[90%] mx-auto space-y-6">
<Tabs
value={activeTab}
onValueChange={(v) => {
setActiveTab(v as TabId);
setSearchTerm("");
}}
onValueChange={handleTabChange}
className="w-full"
>
<TabsList className="grid w-full grid-cols-5">
{tabs.map((tab) => (
<TabsTrigger key={tab.id} value={tab.id}>
{tab.label}
</TabsTrigger>
))}
<TabsList className="w-full">
<TabsTrigger value="badge">{t("Belgilar (Badge)")}</TabsTrigger>
<TabsTrigger value="tarif">{t("Tariflar")}</TabsTrigger>
<TabsTrigger value="transport">{t("Transport")}</TabsTrigger>
{/* <TabsTrigger value="meal">{t("Ovqatlanish")}</TabsTrigger> */}
<TabsTrigger value="hotel_type">{t("Otel turlari")}</TabsTrigger>
<TabsTrigger value="hotel_features">
{t("Otel sharoitlari")}
</TabsTrigger>
</TabsList>
<TabsContent value="badge" className="space-y-4">
<BadgeTable data={data} page={page} pageSize={pageSize} />
</TabsContent>
<TabsContent value="tarif" className="space-y-4">
<TarifTable
data={tarifData}
page={pageTarif}
pageSize={pageSizeTarif}
/>
</TabsContent>
<TabsContent value="transport" className="space-y-4">
<TransportTable
data={transportData}
page={pageTransport}
pageSize={pageSizeTransport}
/>
</TabsContent>
<TabsContent value="hotel_type" className="space-y-4">
<MealTable
data={typeData}
page={pageTransport}
pageSize={pageSizeTransport}
/>
</TabsContent>
<TabsContent value="hotel_features" className="space-y-4">
<FeaturesTable
data={featureData}
page={pageFeature}
pageSize={pageSizeFeature}
setActiveTab={setActiveTab}
setFeatureId={setFeatureId}
/>
</TabsContent>
<TabsContent value="feature_type" className="space-y-4">
<FeaturesTableType
data={featureTypeData}
page={pageFeature}
featureId={featureId}
pageSize={pageSizeFeature}
/>
</TabsContent>
</Tabs>
<Card className="bg-gray-900">
<CardContent className="pt-6">
<div className="flex flex-col sm:flex-row gap-4 justify-between items-center">
<div className="relative w-full sm:w-96">
<Search
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground"
size={20}
/>
<Input
type="text"
placeholder="Qidirish..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<Button
onClick={() => openModal("add")}
className="w-full sm:w-auto cursor-pointer"
>
<Plus size={20} className="mr-2" />
Yangi qo'shish
</Button>
</div>
</CardContent>
</Card>
<Card className="bg-gray-900">
<div className="overflow-x-auto">
<div className="min-w-full">
<div className="border-b">
<div className="flex">
<div className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-20">
ID
</div>
<div className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider flex-1">
Nomi
</div>
{activeTab === "badges" && (
<div className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-48">
Rang
</div>
)}
{(activeTab === "tariffs" || activeTab === "transports") && (
<div className="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider w-32">
Narx
</div>
)}
<div className="px-6 py-3 text-right text-xs font-medium text-muted-foreground uppercase tracking-wider w-32">
Amallar
</div>
</div>
</div>
<div className="divide-y">
{filteredData.length === 0 ? (
<div className="px-6 py-8 text-center text-muted-foreground">
Ma'lumot topilmadi
</div>
) : (
filteredData.map((item) => (
<div
key={item.id}
className="flex items-center hover:bg-accent/50 transition-colors"
>
<div className="px-6 py-4 w-20">{item.id}</div>
<div className="px-6 py-4 font-medium flex-1">
{item.name}
</div>
{activeTab === "badges" && (
<div className="px-6 py-4 w-48">
<div className="flex items-center gap-2">
<div
className="w-6 h-6 rounded border"
style={{ backgroundColor: (item as Badge).color }}
/>
<span>{(item as Badge).color}</span>
</div>
</div>
)}
{(activeTab === "tariffs" ||
activeTab === "transports") && (
<div className="px-6 py-4 w-32">
{(item as Tariff).price}
</div>
)}
<div className="px-6 py-4 w-32">
<div className="flex justify-end gap-2">
<Button
variant="ghost"
size="icon"
onClick={() => openModal("edit", item)}
>
<Edit2 size={18} />
</Button>
<Button
variant="ghost"
size="icon"
onClick={() => handleDelete(item.id)}
>
<Trash2 size={18} className="text-destructive" />
</Button>
</div>
</div>
</div>
))
)}
</div>
</div>
</div>
</Card>
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="sm:max-w-[500px] bg-gray-900">
<DialogHeader>
<DialogTitle>
{modalMode === "add" ? "Yangi qo'shish" : "Tahrirlash"}
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{getFormFields().map((field) => (
<div key={field.name} className="space-y-2">
<Label htmlFor={field.name}>
{field.label}
{field.required && (
<span className="text-destructive">*</span>
)}
</Label>
{field.type === "textarea" ? (
<Textarea
id={field.name}
value={getFieldValue(field.name) as string}
onChange={(e) =>
setFormData({
...formData,
[field.name]: e.target.value,
})
}
required={field.required}
rows={3}
/>
) : field.type === "select" ? (
<Select
value={getFieldValue(field.name) as string}
onValueChange={(value) =>
setFormData({ ...formData, [field.name]: value })
}
required={field.required}
>
<SelectTrigger>
<SelectValue placeholder="Tanlang" />
</SelectTrigger>
<SelectContent>
{field.options?.map((opt) => (
<SelectItem key={opt} value={opt}>
{opt}
</SelectItem>
))}
</SelectContent>
</Select>
) : (
<Input
id={field.name}
type={field.type}
value={getFieldValue(field.name)}
onChange={(e) =>
setFormData({
...formData,
[field.name]:
field.type === "number"
? Number(e.target.value)
: e.target.value,
})
}
required={field.required}
min={field.min}
max={field.max}
/>
)}
</div>
))}
<div className="flex gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={closeModal}
className="flex-1"
>
Bekor qilish
</Button>
<Button type="button" onClick={handleSubmit} className="flex-1">
Saqlash
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</div>
</div>
);