This commit is contained in:
Samandar Turgunboyev
2025-11-01 10:00:42 +05:00
parent 3f8b9881de
commit 08fd482242
5 changed files with 168 additions and 174 deletions

View File

@@ -242,22 +242,22 @@ export default function FinancePage({ user }: { user: Role }) {
<CreditCard size={18} /> <CreditCard size={18} />
{t("Bandlovlar va tolovlar")} {t("Bandlovlar va tolovlar")}
</button> </button>
{user === "moderator" || {(user === "moderator" ||
user === "buxgalter" || user === "buxgalter" ||
user === "admin" || user === "admin" ||
(user === "superuser" && ( user === "superuser") && (
<button <button
className={`px-6 py-3 rounded-lg flex items-center gap-2 transition-all ${ className={`px-6 py-3 rounded-lg flex items-center gap-2 transition-all ${
tab === "agencies" tab === "agencies"
? "bg-blue-600 text-white shadow-md" ? "bg-blue-600 text-white shadow-md"
: "text-gray-400 hover:bg-gray-700" : "text-gray-400 hover:bg-gray-700"
}`} }`}
onClick={() => setTab("agencies")} onClick={() => setTab("agencies")}
> >
<Users size={18} /> <Users size={18} />
{t("Agentlik hisobotlari")} {t("Agentlik hisobotlari")}
</button> </button>
))} )}
</div> </div>
{tab === "bookings" && ( {tab === "bookings" && (
@@ -443,148 +443,146 @@ export default function FinancePage({ user }: { user: Role }) {
</div> </div>
</> </>
)} )}
{user === "moderator" || {(user === "moderator" ||
user === "buxgalter" || user === "buxgalter" ||
user === "admin" || user === "admin" ||
(user === "superuser" && ( user === "superuser") && (
<> <>
{tab === "agencies" && ( {tab === "agencies" && (
<> <>
{agenctLoad ? ( {agenctLoad ? (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 text-white gap-4 w-full"> <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" /> <Loader2 className="w-10 h-10 animate-spin text-cyan-400" />
<p className="text-slate-400"> <p className="text-slate-400">
{t("Ma'lumotlar yuklanmoqda...")} {t("Ma'lumotlar yuklanmoqda...")}
</p> </p>
</div> </div>
) : agencyError ? ( ) : agencyError ? (
<div className="flex flex-col items-center justify-center min-h-screen bg-slate-900 w-full text-center text-white gap-4"> <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" /> <AlertTriangle className="w-10 h-10 text-red-500" />
<p className="text-lg"> <p className="text-lg">
{t("Ma'lumotlarni yuklashda xatolik yuz berdi.")} {t("Ma'lumotlarni yuklashda xatolik yuz berdi.")}
</p> </p>
<Button <Button
onClick={() => agencyRef()} onClick={() => agencyRef()}
className="bg-gradient-to-r from-blue-600 to-cyan-600 text-white rounded-lg px-5 py-2 hover:opacity-90" 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")} {t("Qayta urinish")}
</Button> </Button>
</div> </div>
) : ( ) : (
agencyData && agencyData &&
!agencyError && !agencyError &&
!agenctLoad && ( !agenctLoad && (
<> <>
<h2 className="text-xl font-bold mb-6"> <h2 className="text-xl font-bold mb-6">
{t("Partner Agencies")} {t("Partner Agencies")}
</h2> </h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{agencyData?.data.data.results.map((a) => ( {agencyData?.data.data.results.map((a) => (
<div <div
key={a.id} key={a.id}
className="bg-gray-800 p-6 rounded-xl shadow hover:shadow-md transition-all" className="bg-gray-800 p-6 rounded-xl shadow hover:shadow-md transition-all"
> >
<h2 className="text-xl font-bold mb-3 flex items-center gap-2 text-gray-100"> <h2 className="text-xl font-bold mb-3 flex items-center gap-2 text-gray-100">
<Users className="text-blue-400" size={20} /> <Users className="text-blue-400" size={20} />
{a.name} {a.name}
</h2> </h2>
<div className="grid grid-cols-2 gap-4 mb-4"> <div className="grid grid-cols-2 gap-4 mb-4">
<div className="bg-green-900 p-3 rounded-lg"> <div className="bg-green-900 p-3 rounded-lg">
<p className="text-gray-400 text-sm"> <p className="text-gray-400 text-sm">
{t("Paid")} {t("Paid")}
</p>
<p className="text-green-400 font-bold text-lg">
{formatPrice(a.paid, true)}
</p>
</div>
<div className="bg-yellow-900 p-3 rounded-lg">
<p className="text-gray-400 text-sm">
{t("Pending")}
</p>
<p className="text-yellow-400 font-bold text-lg">
{formatPrice(a.pending, true)}
</p>
</div>
</div>
<div className="mb-4 text-gray-400">
<p className="text-sm mb-1">
{t("Bookings")}:{" "}
<span className="font-medium text-gray-100">
{a.ticket_sold_count}
</span>
</p> </p>
<p className="text-sm"> <p className="text-green-400 font-bold text-lg">
{t("Destinations")}:{" "} {formatPrice(a.paid, true)}
<span className="font-medium text-gray-100">
{a.ticket_count}
</span>
</p> </p>
</div> </div>
<div className="bg-yellow-900 p-3 rounded-lg">
<div className="flex gap-2"> <p className="text-gray-400 text-sm">
<Link {t("Pending")}
to={`/travel/booking/${a.id}`} </p>
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center gap-1 transition-colors flex-1 justify-center" <p className="text-yellow-400 font-bold text-lg">
> {formatPrice(a.pending, true)}
<Eye className="w-4 h-4" /> {t("Ko'rish")} </p>
</Link>
</div> </div>
</div> </div>
))}
</div>
</>
)
)}
<div className="flex justify-end gap-2 mt-5">
<button
disabled={currentPageAgency === 1}
onClick={() =>
setCurrentPageAgency((p) => Math.max(p - 1, 1))
}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
>
<ChevronLeft className="w-5 h-5" />
</button>
{[...Array(agencyData?.data.data.total_pages)].map( <div className="mb-4 text-gray-400">
(_, i) => ( <p className="text-sm mb-1">
<button {t("Bookings")}:{" "}
key={i} <span className="font-medium text-gray-100">
onClick={() => setCurrentPageAgency(i + 1)} {a.ticket_sold_count}
className={`px-4 py-2 rounded-lg border transition-all font-medium ${ </span>
currentPageAgency === i + 1 </p>
? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50" <p className="text-sm">
: "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500" {t("Destinations")}:{" "}
}`} <span className="font-medium text-gray-100">
> {a.ticket_count}
{i + 1} </span>
</button> </p>
), </div>
)}
<div className="flex gap-2">
<Link
to={`/travel/booking/${a.id}`}
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 flex items-center gap-1 transition-colors flex-1 justify-center"
>
<Eye className="w-4 h-4" /> {t("Ko'rish")}
</Link>
</div>
</div>
))}
</div>
</>
)
)}
<div className="flex justify-end gap-2 mt-5">
<button
disabled={currentPageAgency === 1}
onClick={() =>
setCurrentPageAgency((p) => Math.max(p - 1, 1))
}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
>
<ChevronLeft className="w-5 h-5" />
</button>
{[...Array(agencyData?.data.data.total_pages)].map((_, i) => (
<button <button
disabled={ key={i}
currentPageAgency === agencyData?.data.data.total_pages onClick={() => setCurrentPageAgency(i + 1)}
} className={`px-4 py-2 rounded-lg border transition-all font-medium ${
onClick={() => currentPageAgency === i + 1
setCurrentPageAgency((p) => ? "bg-gradient-to-r from-blue-600 to-cyan-600 border-blue-500 text-white shadow-lg shadow-cyan-500/50"
Math.min( : "border-slate-600 text-slate-300 hover:bg-slate-700/50 hover:border-slate-500"
p + 1, }`}
agencyData ? agencyData?.data.data.total_pages : 0,
),
)
}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
> >
<ChevronRight className="w-5 h-5" /> {i + 1}
</button> </button>
</div> ))}
</>
)} <button
</> disabled={
))} currentPageAgency === agencyData?.data.data.total_pages
}
onClick={() =>
setCurrentPageAgency((p) =>
Math.min(
p + 1,
agencyData ? agencyData?.data.data.total_pages : 0,
),
)
}
className="p-2 rounded-lg border border-slate-600 text-slate-300 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed transition-all hover:border-slate-500"
>
<ChevronRight className="w-5 h-5" />
</button>
</div>
</>
)}
</>
)}
</div> </div>
</div> </div>
); );

View File

@@ -109,20 +109,20 @@ export const TourformSchema = z.object({
tariff: z.number().min(1, { message: "Transport ID majburiy" }), tariff: z.number().min(1, { message: "Transport ID majburiy" }),
price: z price: z
.number() .number()
.min(0, { message: "Narx 0 dan kichik bolishi mumkin emas" }), .min(0, { message: "Narx 0 dan kichik bo'lishi mumkin emas" }),
}), }),
) )
.min(1, { message: "Kamida bitta transport tanlang." }), .optional(),
transport: z transport: z
.array( .array(
z.object({ z.object({
transport: z.number().min(1, { message: "Transport ID majburiy" }), transport: z.number().min(1, { message: "Transport ID majburiy" }),
price: z price: z
.number() .number()
.min(0, { message: "Narx 0 dan kichik bolishi mumkin emas" }), .min(0, { message: "Narx 0 dan kichik bo'lishi mumkin emas" }),
}), }),
) )
.min(1, { message: "Kamida bitta transport tanlang." }), .optional(),
banner: z.any().nullable(), banner: z.any().nullable(),
images: z images: z
.array(z.union([z.instanceof(File), z.string()])) .array(z.union([z.instanceof(File), z.string()]))
@@ -199,7 +199,7 @@ export const TourformSchema = z.object({
name_ru: z.string().min(1, { message: "Xizmat nomi (RU) majburiy" }), name_ru: z.string().min(1, { message: "Xizmat nomi (RU) majburiy" }),
price: z price: z
.number() .number()
.min(0, { message: "Narx manfiy bolishi mumkin emas." }), .min(0, { message: "Narx manfiy bo'lishi mumkin emas." }),
}), }),
) )
.optional(), .optional(),

View File

@@ -64,6 +64,7 @@ export interface GetOneTours {
{ {
price: number; price: number;
transport: { transport: {
id: number;
name: string; name: string;
icon_name: string; icon_name: string;
}; };
@@ -394,6 +395,7 @@ export interface GetDetailTours {
transports: { transports: {
price: number; price: number;
transport: { transport: {
id: number;
name: string; name: string;
icon_name: string; icon_name: string;
}; };
@@ -447,9 +449,7 @@ export interface GetDetailTours {
]; ];
tariff: [ tariff: [
{ {
tariff: { tariff: number;
name: string;
};
price: number; price: number;
}, },
]; ];

View File

@@ -330,11 +330,11 @@ const StepOne = ({
if (value.banner instanceof File) { if (value.banner instanceof File) {
formData.append("image_banner", value.banner); formData.append("image_banner", value.banner);
} }
value.tarif.forEach((e, i) => { value.tarif?.forEach((e, i) => {
formData.append(`tariff[${i}]tariff`, String(e.tariff)); formData.append(`tariff[${i}]tariff`, String(e.tariff));
formData.append(`tariff[${i}]price`, String(e.price)); formData.append(`tariff[${i}]price`, String(e.price));
}); });
value.transport.forEach((e, i) => { value.transport?.forEach((e, i) => {
formData.append(`transports[${i}]transport`, String(e.transport)); formData.append(`transports[${i}]transport`, String(e.transport));
formData.append(`transports[${i}]price`, String(e.price)); formData.append(`transports[${i}]price`, String(e.price));
}); });
@@ -1005,11 +1005,10 @@ const StepOne = ({
onChange={(e) => { onChange={(e) => {
const raw = e.target.value.replace(/\D/g, ""); const raw = e.target.value.replace(/\D/g, "");
const num = Number(raw); const num = Number(raw);
const updatedTransport = form const currentTarifs = form.getValues("tarif") || [];
.getValues("tarif") const updatedTransport = currentTarifs.map((t, i) =>
.map((t, i) => i === idx ? { ...t, price: num } : t,
i === idx ? { ...t, price: num } : t, );
);
form.setValue("tarif", updatedTransport); form.setValue("tarif", updatedTransport);
@@ -1024,7 +1023,7 @@ const StepOne = ({
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
const current = form.getValues("tarif"); const current = form.getValues("tarif") || [];
form.setValue( form.setValue(
"tarif", "tarif",
current.filter((_, i) => i !== idx), current.filter((_, i) => i !== idx),
@@ -1057,15 +1056,14 @@ const StepOne = ({
<CommandList> <CommandList>
<CommandGroup heading={t("Mavjud tariflar")}> <CommandGroup heading={t("Mavjud tariflar")}>
{tariff?.data?.data?.results?.map((item: any) => { {tariff?.data?.data?.results?.map((item: any) => {
const selected = form const currentTarifs = form.getValues("tarif") || [];
.getValues("tarif") const selected = currentTarifs.some((t) => t.tariff === item.id);
.some((t) => t.tariff === item.id);
return ( return (
<CommandItem <CommandItem
key={item.id} key={item.id}
onSelect={() => { onSelect={() => {
const current = form.getValues("tarif"); const current = form.getValues("tarif") || [];
if (selected) { if (selected) {
form.setValue( form.setValue(
"tarif", "tarif",
@@ -1139,11 +1137,10 @@ const StepOne = ({
onChange={(e) => { onChange={(e) => {
const raw = e.target.value.replace(/\D/g, ""); const raw = e.target.value.replace(/\D/g, "");
const num = Number(raw); const num = Number(raw);
const updatedTransport = form const currentTransports = form.getValues("transport") || [];
.getValues("transport") const updatedTransport = currentTransports.map((t, i) =>
.map((t, i) => i === idx ? { ...t, price: num } : t,
i === idx ? { ...t, price: num } : t, );
);
form.setValue("transport", updatedTransport); form.setValue("transport", updatedTransport);
@@ -1158,7 +1155,7 @@ const StepOne = ({
<button <button
type="button" type="button"
onClick={() => { onClick={() => {
const current = form.getValues("transport"); const current = form.getValues("transport") || [];
form.setValue( form.setValue(
"transport", "transport",
current.filter((_, i) => i !== idx), current.filter((_, i) => i !== idx),
@@ -1193,15 +1190,14 @@ const StepOne = ({
<CommandList> <CommandList>
<CommandGroup heading={t("Mavjud transportlar")}> <CommandGroup heading={t("Mavjud transportlar")}>
{transport?.data?.data?.results?.map((item: any) => { {transport?.data?.data?.results?.map((item: any) => {
const selected = form const currentTransports = form.getValues("transport") || [];
.getValues("transport") const selected = currentTransports.some((t) => t.transport === item.id);
.some((t) => t.transport === item.id);
return ( return (
<CommandItem <CommandItem
key={item.id} key={item.id}
onSelect={() => { onSelect={() => {
const current = form.getValues("transport"); const current = form.getValues("transport") || [];
if (selected) { if (selected) {
form.setValue( form.setValue(
"transport", "transport",

View File

@@ -145,7 +145,7 @@ const Tours = ({ user }: { user: Role }) => {
<Button <Button
onClick={() => navigate("/tours/create")} onClick={() => navigate("/tours/create")}
variant="default" variant="default"
disabled={user !== "tour_admin"} // disabled={user !== "tour_admin"}
> >
<PlusCircle className="w-5 h-5 mr-2" /> {t("Yangi tur qo'shish")} <PlusCircle className="w-5 h-5 mr-2" /> {t("Yangi tur qo'shish")}
</Button> </Button>