status order

This commit is contained in:
Samandar Turgunboyev
2025-11-04 11:51:52 +05:00
parent 3481d49bb9
commit 89e4189850
7 changed files with 270 additions and 90 deletions

View File

@@ -11,7 +11,7 @@ import Currency from './pages/Currency';
import Employees from './pages/Employees'; import Employees from './pages/Employees';
import Login from './pages/Login'; import Login from './pages/Login';
import Payments from './pages/Payments'; import Payments from './pages/Payments';
import Permissions from './pages/Permissions'; // Yangi sahifa import Permissions from './pages/Permissions';
import Reference from './pages/Reference'; import Reference from './pages/Reference';
import Warhouses from './pages/Warhouses'; import Warhouses from './pages/Warhouses';

View File

@@ -1,13 +1,29 @@
import { Search } from 'lucide-react'; import { Search } from 'lucide-react';
import { Dispatch, SetStateAction } from 'react';
const Searchs = ({ searchTerm, loading, setSearchTerm, placeholder = 'Ism, telefon yoki manzil boyicha qidirish...' }) => { interface Props {
searchTerm: string;
loading: boolean;
setSearchTerm: Dispatch<SetStateAction<string>>;
placeholder: string;
}
const Searchs = ({ searchTerm, loading, setSearchTerm, placeholder = 'Qidirish' }: Props) => {
return ( return (
<div className="mb-4"> <div className="mb-4">
<label htmlFor="search" className="sr-only"> <label htmlFor="search" className="sr-only">
Qidiruv Qidiruv
</label> </label>
<div className="relative max-w-md"> <div className="relative max-w-md">
<input type="text" id="search" placeholder={placeholder} value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} className="w-full border border-slate-300 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]" disabled={loading} /> <input
type="text"
id="search"
placeholder={placeholder}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full border border-slate-300 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]"
disabled={loading}
/>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-400" /> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-400" />
</div> </div>
</div> </div>

View File

@@ -2,8 +2,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Edit, Trash2, Users } from 'lucide-react'; import { Edit, Trash2, Users } from 'lucide-react';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import axiosInstance from '../../api/axiosInstance'; import axiosInstance from '../../api/axiosInstance';
import Searchs from '../../components/Searchs';
import Pagination from '../Pagination'; import Pagination from '../Pagination';
import Searchs from '../Searchs';
const ClientList = ({ alert, setAlert, setClients, setCargoId }) => { const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
const client = useQueryClient(); const client = useQueryClient();
@@ -24,7 +24,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
axiosInstance.get('/clients/list', { axiosInstance.get('/clients/list', {
params: { params: {
page: currentPage, page: currentPage,
sort: 'id', sort: 'active',
clientName: searchTerm, clientName: searchTerm,
direction: 'desc', direction: 'desc',
}, },
@@ -44,7 +44,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
}; };
const { mutate: deleteClient, isPending: deleteLoad } = useMutation({ const { mutate: deleteClient, isPending: deleteLoad } = useMutation({
mutationFn: (id: number) => axiosInstance.delete(`/users/delete/${id}`), mutationFn: (id) => axiosInstance.delete(`/users/delete/${id}`),
mutationKey: ['clientsDelete'], mutationKey: ['clientsDelete'],
onSuccess: () => { onSuccess: () => {
client.invalidateQueries({ queryKey: ['clients'] }); client.invalidateQueries({ queryKey: ['clients'] });
@@ -57,7 +57,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
}, },
}); });
const handlePageChange = (page: number) => { const handlePageChange = (page) => {
setCurrentPage(page); setCurrentPage(page);
}; };
@@ -81,7 +81,10 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
<th className="text-left p-4">Passport seriyasi</th> <th className="text-left p-4">Passport seriyasi</th>
<th className="text-left p-4">PNFL</th> <th className="text-left p-4">PNFL</th>
<th className="text-left p-4">Tug'ilgan sana</th> <th className="text-left p-4">Tug'ilgan sana</th>
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && <th className="text-left p-4">Amallar</th>} <th className="text-left p-4">Holati</th>
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && (
<th className="text-left p-4">Amallar</th>
)}
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-200"> <tbody className="divide-y divide-slate-200">
@@ -95,14 +98,32 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
<td className="p-4">{client.passportSeries}</td> <td className="p-4">{client.passportSeries}</td>
<td className="p-4">{client.pinfl}</td> <td className="p-4">{client.pinfl}</td>
<td className="p-4">{client.dateOfBirth}</td> <td className="p-4">{client.dateOfBirth}</td>
{/* <label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={client.active}
onChange={handleActive}
/>
<div className="w-11 h-6 bg-gray-300 rounded-full peer peer-checked:bg-blue-600 peer-focus:ring-2 peer-focus:ring-blue-400 transition-colors duration-300"></div>
<div className="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform duration-300 peer-checked:translate-x-5"></div>
</label> */}
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && ( {(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && (
<td className="p-4"> <td className="p-4">
<div className="flex gap-2"> <div className="flex gap-2">
<button onClick={() => handleEdit(client.id, client.aviaCargoId)} className="flex items-center gap-1 border border-yellow-300 text-yellow-700 hover:bg-yellow-50 px-3 py-1 rounded-lg" disabled={isLoading}> <button
onClick={() => handleEdit(client.id, client.aviaCargoId)}
className="flex items-center gap-1 border border-yellow-300 text-yellow-700 hover:bg-yellow-50 px-3 py-1 rounded-lg"
disabled={isLoading}
>
<Edit className="h-4 w-4" /> <Edit className="h-4 w-4" />
<span className="hidden sm:inline">Tahrirlash</span> <span className="hidden sm:inline">Tahrirlash</span>
</button> </button>
<button onClick={() => deleteClient(client.id)} className="flex items-center gap-1 border border-red-300 text-red-700 hover:bg-red-50 px-3 py-1 rounded-lg" disabled={deleteLoad}> <button
onClick={() => deleteClient(client.id)}
className="flex items-center gap-1 border border-red-300 text-red-700 hover:bg-red-50 px-3 py-1 rounded-lg"
disabled={deleteLoad}
>
<Trash2 className="h-4 w-4" /> <Trash2 className="h-4 w-4" />
<span className="hidden sm:inline">O'chirish</span> <span className="hidden sm:inline">O'chirish</span>
</button> </button>
@@ -116,7 +137,13 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
</div> </div>
</div> </div>
<div className="flex items-end mt-5 justify-end"> <div className="flex items-end mt-5 justify-end">
<Pagination currentPage={currentPage} totalPages={data?.totalPages || 1} totalElements={data?.totalElements || 0} onPageChange={handlePageChange} isLoading={isLoading} /> <Pagination
currentPage={currentPage}
totalPages={data?.totalPages || 1}
totalElements={data?.totalElements || 0}
onPageChange={handlePageChange}
isLoading={isLoading}
/>
</div> </div>
</> </>
); );

View File

@@ -80,7 +80,7 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
const { data: client, refetch: clientRef } = useQuery({ const { data: client, refetch: clientRef } = useQuery({
queryKey: ['client_one'], queryKey: ['client_one'],
queryFn: async () => axiosInstance.get('/clients/list', { params: { cargoId } }), queryFn: async () => axiosInstance.get('/clients/list', { params: { cargoId } }),
select: (data) => data.data.data as { data: any[] }, select: (data) => data.data.data,
}); });
const { data, refetch } = useQuery({ const { data, refetch } = useQuery({
@@ -102,23 +102,24 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
phone: c.phone, phone: c.phone,
pinfl: c.pinfl, pinfl: c.pinfl,
}); });
setActive(c.active);
} }
clientRef(); clientRef();
refetch(); refetch();
}, [cargoId, client, refetch, clientRef]); }, [cargoId, client, refetch, clientRef]);
// useEffect(() => { useEffect(() => {
// if (currentPassport) { if (currentPassport) {
// setFormData((prev) => ({ setFormData((prev) => ({
// ...prev, ...prev,
// fullName: currentPassport.fullName || '', fullName: currentPassport.fullName || '',
// passportSeries: currentPassport.passportSeries || '', passportSeries: currentPassport.passportSeries || '',
// pinfl: currentPassport.passportPin || '', pinfl: currentPassport.passportPin || '',
// dateOfBirth: formatDate(currentPassport.birthDate), dateOfBirth: formatDate(currentPassport.birthDate),
// })); }));
// setActive(currentPassport.active); setActive(currentPassport.active);
// } }
// }, [currentPassport]); }, [currentPassport]);
const { mutate: createMutate } = useMutation({ const { mutate: createMutate } = useMutation({
mutationFn: async (params: any) => { mutationFn: async (params: any) => {
@@ -202,6 +203,8 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
setAlert({ type: 'success', message: "Passport ma'lumotlari yangilandi." }); setAlert({ type: 'success', message: "Passport ma'lumotlari yangilandi." });
}; };
console.log(active);
return ( return (
<div> <div>
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && ( {(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && (
@@ -211,7 +214,15 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
{clients ? 'Mijozni tahrirlash' : "Yangi mijoz qo'shish"} {clients ? 'Mijozni tahrirlash' : "Yangi mijoz qo'shish"}
</h2> </h2>
{clients && data && data.length > 0 && <PassportCarousel passports={data} onUpdate={handlePassportUpdate} setTargetImage={setTargetImage} setOpenModal={setOpenModal} setCurrentPassport={setCurrentPassport} />} {clients && data && data.length > 0 && (
<PassportCarousel
passports={data}
onUpdate={handlePassportUpdate}
setTargetImage={setTargetImage}
setOpenModal={setOpenModal}
setCurrentPassport={setCurrentPassport}
/>
)}
<form className="space-y-6" onSubmit={onSubmit}> <form className="space-y-6" onSubmit={onSubmit}>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-10"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 mt-10">
@@ -225,13 +236,24 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
].map((field) => ( ].map((field) => (
<div key={field.name}> <div key={field.name}>
<label className="block text-sm font-medium mb-1">{field.label}</label> <label className="block text-sm font-medium mb-1">{field.label}</label>
<input type={field.type} name={field.name} value={formData[field.name as keyof FormData]} onChange={handleChange} className="w-full border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]" /> <input
type={field.type}
name={field.name}
value={formData[field.name as keyof FormData]}
onChange={handleChange}
className="w-full border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]"
/>
</div> </div>
))} ))}
{clients && currentPassport && ( {clients && currentPassport && (
<label className="relative inline-flex items-center cursor-pointer"> <label className="relative inline-flex items-center cursor-pointer">
<input type="checkbox" className="sr-only peer" checked={active} onChange={handleActive} /> <input
type="checkbox"
className="sr-only peer"
checked={active}
onChange={handleActive}
/>
<div className="w-11 h-6 bg-gray-300 rounded-full peer peer-checked:bg-blue-600 peer-focus:ring-2 peer-focus:ring-blue-400 transition-colors duration-300"></div> <div className="w-11 h-6 bg-gray-300 rounded-full peer peer-checked:bg-blue-600 peer-focus:ring-2 peer-focus:ring-blue-400 transition-colors duration-300"></div>
<div className="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform duration-300 peer-checked:translate-x-5"></div> <div className="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform duration-300 peer-checked:translate-x-5"></div>
</label> </label>
@@ -239,22 +261,42 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
</div> </div>
<div className="flex flex-col sm:flex-row gap-3 pt-4"> <div className="flex flex-col sm:flex-row gap-3 pt-4">
<button type="submit" className="bg-[#3489e3] hover:bg-blue-500 text-white px-6 py-2 rounded-lg disabled:opacity-60"> <button
type="submit"
className="bg-[#3489e3] hover:bg-blue-500 text-white px-6 py-2 rounded-lg disabled:opacity-60"
>
{clients ? 'Yangilash' : "Qo'shish"} {clients ? 'Yangilash' : "Qo'shish"}
</button> </button>
<button type="button" className="border border-slate-300 text-slate-700 hover:bg-slate-50 px-6 py-2 rounded-lg"> <button
type="button"
className="border border-slate-300 text-slate-700 hover:bg-slate-50 px-6 py-2 rounded-lg"
>
Bekor qilish Bekor qilish
</button> </button>
</div> </div>
</form> </form>
</div> </div>
)} )}
{tagetIamge && opneModal && <ModalIamge image={tagetIamge} setTargetImage={setTargetImage} setOpenModal={setOpenModal} />} {tagetIamge && opneModal && (
<ModalIamge
image={tagetIamge}
setTargetImage={setTargetImage}
setOpenModal={setOpenModal}
/>
)}
</div> </div>
); );
}; };
const ModalIamge = ({ image, setTargetImage, setOpenModal }: { image: string; setTargetImage: (img: string | null) => void; setOpenModal: (open: boolean) => void }) => { const ModalIamge = ({
image,
setTargetImage,
setOpenModal,
}: {
image: string;
setTargetImage: (img: string | null) => void;
setOpenModal: (open: boolean) => void;
}) => {
return ( return (
<div className="w-full h-screen bg-black/60 fixed top-0 bottom-0 left-0 z-50 overflow-hidden"> <div className="w-full h-screen bg-black/60 fixed top-0 bottom-0 left-0 z-50 overflow-hidden">
<button <button

View File

@@ -1,7 +1,17 @@
'use client'; 'use client';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { AlertCircle, CheckCircle, Clock, CreditCard, Package, Phone, User, X, XCircle } from 'lucide-react'; import {
AlertCircle,
CheckCircle,
Clock,
CreditCard,
Package,
Phone,
User,
X,
XCircle,
} from 'lucide-react';
import { useEffect } from 'react'; import { useEffect } from 'react';
import axiosInstance from '../../api/axiosInstance'; import axiosInstance from '../../api/axiosInstance';
import { cn } from '../../lib/utils'; import { cn } from '../../lib/utils';
@@ -71,7 +81,9 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
return new Intl.NumberFormat('uz-UZ').format(amount) + " so'm"; return new Intl.NumberFormat('uz-UZ').format(amount) + " so'm";
}; };
const currentStatus = data ? paymentStatuses.find((status) => status.value === data.paymentStatus) : paymentStatuses[0]; const currentStatus = data
? paymentStatuses.find((status) => status.value === data.paymentStatus)
: paymentStatuses[0];
const CurrentIcon = currentStatus ? currentStatus.icon : paymentStatuses[0]; const CurrentIcon = currentStatus ? currentStatus.icon : paymentStatuses[0];
return ( return (
@@ -80,7 +92,10 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
<div className="relative bg-white rounded-xl shadow-2xl w-[800px] max-w-[95vw] max-h-[90vh] overflow-hidden"> <div className="relative bg-white rounded-xl shadow-2xl w-[800px] max-w-[95vw] max-h-[90vh] overflow-hidden">
<div className="flex items-center justify-between p-6 border-b border-gray-200"> <div className="flex items-center justify-between p-6 border-b border-gray-200">
<h2 className="text-xl font-semibold text-gray-900">To'lov ma'lumotlari</h2> <h2 className="text-xl font-semibold text-gray-900">To'lov ma'lumotlari</h2>
<button onClick={onClose} className="p-2 rounded-full hover:bg-gray-100 transition-colors"> <button
onClick={onClose}
className="p-2 rounded-full hover:bg-gray-100 transition-colors"
>
<X className="w-5 h-5 text-gray-500" /> <X className="w-5 h-5 text-gray-500" />
</button> </button>
</div> </div>
@@ -90,7 +105,10 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
<div className="flex flex-col items-center justify-center py-12"> <div className="flex flex-col items-center justify-center py-12">
<XCircle className="w-12 h-12 text-red-400 mb-3" /> <XCircle className="w-12 h-12 text-red-400 mb-3" />
<p className="text-red-600 text-center">Ma'lumotlarni yuklashda xatolik yuz berdi</p> <p className="text-red-600 text-center">Ma'lumotlarni yuklashda xatolik yuz berdi</p>
<button onClick={() => refetch()} className="mt-3 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"> <button
onClick={() => refetch()}
className="mt-3 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Qayta urinish Qayta urinish
</button> </button>
</div> </div>
@@ -125,7 +143,14 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
</div> </div>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<span className="font-medium text-lg text-gray-600">To'lov holati:</span> <span className="font-medium text-lg text-gray-600">To'lov holati:</span>
<button className={cn('inline-flex w-fit items-center gap-1 px-2 py-1 rounded-full font-medium text-lg', currentStatus.bgColor, currentStatus.borderColor, currentStatus.color)}> <button
className={cn(
'inline-flex w-fit items-center gap-1 px-2 py-1 rounded-full font-medium text-lg',
currentStatus.bgColor,
currentStatus.borderColor,
currentStatus.color
)}
>
<CurrentIcon className="h-4 w-4" /> <CurrentIcon className="h-4 w-4" />
<span className="font-medium">{currentStatus.label}</span> <span className="font-medium">{currentStatus.label}</span>
</button> </button>
@@ -162,11 +187,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
<button <button
className={cn( className={cn(
'inline-flex w-fit items-center gap-1 px-2 py-1 rounded-full font-medium text-lg', 'inline-flex w-fit items-center gap-1 px-2 py-1 rounded-full font-medium text-lg',
data.paymentType === null ? 'bg-red-50 text-red-600 border-red-200' : data.paymentType === 'CASH' ? 'bg-green-50 text-green-600 border-green-200' : 'bg-green-50 text-green-600 border-green-200' data.paymentType === null
? 'bg-red-50 text-red-600 border-red-200'
: data.paymentType === 'CASH'
? 'bg-green-50 text-green-600 border-green-200'
: 'bg-green-50 text-green-600 border-green-200'
)} )}
> >
<CurrentIcon className="h-4 w-4" /> <CurrentIcon className="h-4 w-4" />
<span className="font-medium">{data.paymentType === null ? "To'lanmagan" : data.paymentType === 'CASH' ? 'Naqd' : data.paymentType}</span> <span className="font-medium">
{data.paymentType === null
? "To'lanmagan"
: data.paymentType === 'CASH'
? 'Naqd'
: data.paymentType}
</span>
</button> </button>
</div> </div>
</div> </div>
@@ -183,16 +218,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
{data.clickTransactions.map((transaction, index) => ( {data.clickTransactions.map((transaction, index) => (
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-white"> <div key={index} className="border border-gray-200 rounded-lg p-4 bg-white">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<span className="font-medium text-lg text-gray-900">ID: {transaction.transactionId}</span> <span className="font-medium text-lg text-gray-900">
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg ${getStatusColor(transaction.status)}`}> ID: {transaction.transactionId}
{getStatusIcon(transaction.status)} </span>
<span
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg`}
>
{transaction.status} {transaction.status}
</span> </span>
</div> </div>
<div className="grid grid-cols-2 gap-3 text-sm"> <div className="grid grid-cols-2 gap-3 text-sm">
<div> <div>
<span className="font-medium text-lg text-gray-600">Summa:</span> <span className="font-medium text-lg text-gray-600">Summa:</span>
<p className="text-gray-900 font-semibold">{formatAmount(transaction.amount)}</p> <p className="text-gray-900 font-semibold">
{formatAmount(transaction.amount)}
</p>
</div> </div>
<div> <div>
<span className="font-medium text-lg text-gray-600">Provayder:</span> <span className="font-medium text-lg text-gray-600">Provayder:</span>
@@ -204,13 +244,19 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
</div> </div>
{transaction.completeTime && ( {transaction.completeTime && (
<div> <div>
<span className="font-medium text-lg text-gray-600">Tugallangan:</span> <span className="font-medium text-lg text-gray-600">
<p className="text-gray-900">{formatDate(transaction.completeTime)}</p> Tugallangan:
</span>
<p className="text-gray-900">
{formatDate(transaction.completeTime)}
</p>
</div> </div>
)} )}
{transaction.cancelTime && ( {transaction.cancelTime && (
<div> <div>
<span className="font-medium text-lg text-gray-600">Bekor qilingan:</span> <span className="font-medium text-lg text-gray-600">
Bekor qilingan:
</span>
<p className="text-gray-900">{formatDate(transaction.cancelTime)}</p> <p className="text-gray-900">{formatDate(transaction.cancelTime)}</p>
</div> </div>
)} )}
@@ -239,16 +285,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
{data.paymeTransactions.map((transaction, index) => ( {data.paymeTransactions.map((transaction, index) => (
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-white"> <div key={index} className="border border-gray-200 rounded-lg p-4 bg-white">
<div className="flex items-center justify-between mb-3"> <div className="flex items-center justify-between mb-3">
<span className="font-medium text-lg text-gray-900">ID: {transaction.transactionId}</span> <span className="font-medium text-lg text-gray-900">
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg ${getStatusColor(transaction.status)}`}> ID: {transaction.transactionId}
{getStatusIcon(transaction.status)} </span>
<span
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg`}
>
{transaction.status} {transaction.status}
</span> </span>
</div> </div>
<div className="grid grid-cols-2 gap-3 text-sm"> <div className="grid grid-cols-2 gap-3 text-sm">
<div> <div>
<span className="font-medium text-lg text-gray-600">Summa:</span> <span className="font-medium text-lg text-gray-600">Summa:</span>
<p className="text-gray-900 font-semibold">{formatAmount(transaction.amount)}</p> <p className="text-gray-900 font-semibold">
{formatAmount(transaction.amount)}
</p>
</div> </div>
<div> <div>
<span className="font-medium text-lg text-gray-600">Provayder:</span> <span className="font-medium text-lg text-gray-600">Provayder:</span>
@@ -260,13 +311,19 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
</div> </div>
{transaction.completeTime && ( {transaction.completeTime && (
<div> <div>
<span className="font-medium text-lg text-gray-600">Tugallangan:</span> <span className="font-medium text-lg text-gray-600">
<p className="text-gray-900">{formatDate(transaction.completeTime)}</p> Tugallangan:
</span>
<p className="text-gray-900">
{formatDate(transaction.completeTime)}
</p>
</div> </div>
)} )}
{transaction.cancelTime && ( {transaction.cancelTime && (
<div> <div>
<span className="font-medium text-lg text-gray-600">Bekor qilingan:</span> <span className="font-medium text-lg text-gray-600">
Bekor qilingan:
</span>
<p className="text-gray-900">{formatDate(transaction.cancelTime)}</p> <p className="text-gray-900">{formatDate(transaction.cancelTime)}</p>
</div> </div>
)} )}
@@ -285,7 +342,11 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
</div> </div>
)} )}
{(!data.clickTransactions || data.clickTransactions.length === 0) && (!data.paymeTransactions || data.paymeTransactions.length === 0) && !(['PAYED', 'PENDING'].includes(data.paymentStatus) && data.paymentType === 'CASH') && ( {(!data.clickTransactions || data.clickTransactions.length === 0) &&
(!data.paymeTransactions || data.paymeTransactions.length === 0) &&
!(
['PAYED', 'PENDING'].includes(data.paymentStatus) && data.paymentType === 'CASH'
) && (
<div className="text-center py-8"> <div className="text-center py-8">
<Clock className="w-12 h-12 text-gray-400 mx-auto mb-3" /> <Clock className="w-12 h-12 text-gray-400 mx-auto mb-3" />
<p className="text-gray-500">Hozircha tranzaksiyalar mavjud emas</p> <p className="text-gray-500">Hozircha tranzaksiyalar mavjud emas</p>

View File

@@ -40,18 +40,14 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
const [position, setPosition] = useState({ top: 0, left: 0, width: 0 }); const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
const buttonRef = useRef(null); const buttonRef = useRef(null);
const currentStatus = paymentStatuses.find((status) => status.value === value) || paymentStatuses[0]; const currentStatus =
paymentStatuses.find((status) => status.value === value) || paymentStatuses[0];
const CurrentIcon = currentStatus.icon; const CurrentIcon = currentStatus.icon;
const handleSelect = (statusValue) => { const handleSelect = (statusValue) => {
if (statusValue === 'PAID' && (value === 'UNPAID' || value === 'PENDING')) { // Faqat PAYED holatiga o'tkazish
if (statusValue === 'PAYED' && value !== 'PAYED') {
onChange('PAYED'); onChange('PAYED');
} else if (statusValue === 'PAID' && value === 'PAID') {
// Already paid, do nothing
return;
} else if (statusValue !== 'PAID') {
// Show warning for non-PAID status changes
return;
} }
setIsOpen(false); setIsOpen(false);
}; };
@@ -88,17 +84,18 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
<CurrentIcon className="h-4 w-4" /> <CurrentIcon className="h-4 w-4" />
<span className="font-medium">{currentStatus.label}</span> <span className="font-medium">{currentStatus.label}</span>
</div> </div>
{!disabled && <ChevronDown className={cn('h-4 w-4 transition-transform duration-200', isOpen && 'rotate-180')} />} {!disabled && (
<ChevronDown
className={cn('h-4 w-4 transition-transform duration-200', isOpen && 'rotate-180')}
/>
)}
</button> </button>
{/* Options (portal) */} {/* Options (portal) */}
{isOpen && {isOpen &&
createPortal( createPortal(
<> <>
{/* Backdrop */}
<div className="fixed inset-0 z-40" onClick={() => setIsOpen(false)} /> <div className="fixed inset-0 z-40" onClick={() => setIsOpen(false)} />
{/* Dropdown */}
<div <div
className="absolute bg-white border border-slate-200 rounded-lg shadow-lg z-50 overflow-hidden" className="absolute bg-white border border-slate-200 rounded-lg shadow-lg z-50 overflow-hidden"
style={{ style={{
@@ -111,17 +108,24 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
{paymentStatuses.map((status) => { {paymentStatuses.map((status) => {
const StatusIcon = status.icon; const StatusIcon = status.icon;
const isSelected = status.value === value; const isSelected = status.value === value;
const isDisabled = status.value !== 'PAID' || value === 'PAID';
return ( return (
<button <button
key={status.value} key={status.value}
type="button" type="button"
onClick={() => !isDisabled && handleSelect(status.value)} onClick={() => handleSelect(status.value)}
disabled={isDisabled} className={cn(
className={cn('flex items-center gap-3 w-full px-3 py-2.5 text-sm transition-colors duration-150', isDisabled ? 'opacity-50 cursor-not-allowed' : 'hover:bg-slate-50 focus:bg-slate-50 focus:outline-none cursor-pointer', isSelected && 'bg-slate-100')} 'flex items-center gap-3 w-full px-3 py-2.5 text-sm hover:bg-slate-50 transition-colors duration-150'
)}
>
<div
className={cn(
'flex items-center justify-center w-6 h-6 rounded-full',
status.bgColor,
status.borderColor,
'border'
)}
> >
<div className={cn('flex items-center justify-center w-6 h-6 rounded-full', status.bgColor, status.borderColor, 'border')}>
<StatusIcon className={cn('h-3 w-3', status.color)} /> <StatusIcon className={cn('h-3 w-3', status.color)} />
</div> </div>
<span className={cn('font-medium', status.color)}>{status.label}</span> <span className={cn('font-medium', status.color)}>{status.label}</span>

View File

@@ -2,7 +2,7 @@
import { useMutation, useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { QrCode, Search, Users } from 'lucide-react'; import { QrCode, Search, Users } from 'lucide-react';
import { useState } from 'react'; import { useEffect, useState } from 'react';
import axiosInstance from '../../api/axiosInstance'; import axiosInstance from '../../api/axiosInstance';
import Pagination from '../Pagination'; import Pagination from '../Pagination';
import ModalPayment from './ModalPayment'; import ModalPayment from './ModalPayment';
@@ -17,15 +17,17 @@ const PaymentsList = ({ setAlert }) => {
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false); const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
const [scannedResult, setScannedResult] = useState(null); const [scannedResult, setScannedResult] = useState(null);
const [packetid, setPacketId] = useState(); const [packetid, setPacketId] = useState();
const [userRole, setUserRole] = useState('');
const userRole = (() => { useEffect(() => {
try {
const storedUser = localStorage.getItem('user'); const storedUser = localStorage.getItem('user');
return storedUser ? JSON.parse(storedUser).user.role : null; if (storedUser) {
} catch { const parsed = JSON.parse(storedUser);
return null; setUserRole(parsed.user?.role || null);
} }
})(); }, []);
console.log(userRole);
const { data, isLoading, refetch } = useQuery({ const { data, isLoading, refetch } = useQuery({
queryKey: ['payments_list', currentPage, searchTerm, cargoType], queryKey: ['payments_list', currentPage, searchTerm, cargoType],
@@ -36,6 +38,7 @@ const PaymentsList = ({ setAlert }) => {
cargoType, cargoType,
size: 30, size: 30,
search: searchTerm, search: searchTerm,
sort: 'paymentStatus',
}, },
}), }),
select(data) { select(data) {
@@ -86,7 +89,11 @@ const PaymentsList = ({ setAlert }) => {
refetch(); refetch();
}} }}
/> />
<ModalPayment packetId={packetid} isOpen={isPaymentModalOpen} onClose={() => setIsPaymentModalOpen(false)} /> <ModalPayment
packetId={packetid}
isOpen={isPaymentModalOpen}
onClose={() => setIsPaymentModalOpen(false)}
/>
<div className="relative flex gap-4"> <div className="relative flex gap-4">
<div className="mb-4 w-[90%]"> <div className="mb-4 w-[90%]">
@@ -97,7 +104,7 @@ const PaymentsList = ({ setAlert }) => {
<input <input
type="text" type="text"
id="search" id="search"
placeholder={"Cargo Ids, Partiya nomi, Paket nomi bo'yicha qidirish"} placeholder={"Cargo Ids, Paket nomi bo'yicha qidirish"}
value={searchTerm} value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
className="w-full border border-slate-300 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]" className="w-full border border-slate-300 rounded-lg pl-10 pr-4 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]"
@@ -106,7 +113,11 @@ const PaymentsList = ({ setAlert }) => {
</div> </div>
</div> </div>
<div className="mb-4 w-[10%]"> <div className="mb-4 w-[10%]">
<select name="role" className="w-full border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]" onChange={(e) => setCargoType(e.target.value)}> <select
name="role"
className="w-full border border-slate-300 rounded-lg px-3 py-2 focus:outline-none focus:ring-2 focus:ring-[#3489e3]"
onChange={(e) => setCargoType(e.target.value)}
>
<option value="">Barchasi</option> <option value="">Barchasi</option>
<option value="AVIA">AVIA</option> <option value="AVIA">AVIA</option>
<option value="AUTO">AUTO</option> <option value="AUTO">AUTO</option>
@@ -115,7 +126,10 @@ const PaymentsList = ({ setAlert }) => {
</div> </div>
<div className="flex justify-end mb-4"> <div className="flex justify-end mb-4">
<button className="bg-[#28A7E8] rounded-md p-2 cursor-pointer hover:bg-[#2196d3] transition-colors" onClick={() => setIsQrModalOpen(true)}> <button
className="bg-[#28A7E8] rounded-md p-2 cursor-pointer hover:bg-[#2196d3] transition-colors"
onClick={() => setIsQrModalOpen(true)}
>
<QrCode className="size-6 text-white" /> <QrCode className="size-6 text-white" />
</button> </button>
</div> </div>
@@ -162,10 +176,20 @@ const PaymentsList = ({ setAlert }) => {
<td className="p-4">{client.cargoId}</td> <td className="p-4">{client.cargoId}</td>
<td className="p-4">{client.partyName}</td> <td className="p-4">{client.partyName}</td>
<td className="p-4 max-w-3xs">{client.packetName}</td> <td className="p-4 max-w-3xs">{client.packetName}</td>
{client.paymentType === null ? <td className="p-4 text-red-500">To'lanmagan</td> : <td className="p-4 text-green-600">{client.paymentType === 'CASH' ? 'Naxt' : client.paymentType}</td>} {client.paymentType === null ? (
<td className="p-4 text-red-500">To'lanmagan</td>
) : (
<td className="p-4 text-green-600">
{client.paymentType === 'CASH' ? 'Naxt' : client.paymentType}
</td>
)}
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && ( {(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && (
<td className="p-4"> <td className="p-4">
<PaymentStatusSelector value={client.paymentStatus || 'NEW'} onChange={(value) => handlePaymentStatusChange(client.packetId, value)} disabled={client.paymentStatus === 'PAYED'} /> <PaymentStatusSelector
value={client.paymentStatus || 'NEW'}
onChange={(value) => handlePaymentStatusChange(client.packetId, value)}
disabled={client.paymentStatus === 'PAYED'}
/>
</td> </td>
)} )}
<td className="p-4"> <td className="p-4">
@@ -187,7 +211,13 @@ const PaymentsList = ({ setAlert }) => {
</div> </div>
<div className="flex items-end mt-5 justify-end"> <div className="flex items-end mt-5 justify-end">
<Pagination currentPage={currentPage} totalPages={data?.totalPages || 1} totalElements={data?.totalElements || 0} onPageChange={handlePageChange} isLoading={isLoading} /> <Pagination
currentPage={currentPage}
totalPages={data?.totalPages || 1}
totalElements={data?.totalElements || 0}
onPageChange={handlePageChange}
isLoading={isLoading}
/>
</div> </div>
</> </>
); );