status order
This commit is contained in:
@@ -11,7 +11,7 @@ import Currency from './pages/Currency';
|
||||
import Employees from './pages/Employees';
|
||||
import Login from './pages/Login';
|
||||
import Payments from './pages/Payments';
|
||||
import Permissions from './pages/Permissions'; // Yangi sahifa
|
||||
import Permissions from './pages/Permissions';
|
||||
import Reference from './pages/Reference';
|
||||
import Warhouses from './pages/Warhouses';
|
||||
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import { Search } from 'lucide-react';
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
|
||||
const Searchs = ({ searchTerm, loading, setSearchTerm, placeholder = 'Ism, telefon yoki manzil bo‘yicha qidirish...' }) => {
|
||||
interface Props {
|
||||
searchTerm: string;
|
||||
loading: boolean;
|
||||
setSearchTerm: Dispatch<SetStateAction<string>>;
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
const Searchs = ({ searchTerm, loading, setSearchTerm, placeholder = 'Qidirish' }: Props) => {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<label htmlFor="search" className="sr-only">
|
||||
Qidiruv
|
||||
</label>
|
||||
<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" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { Edit, Trash2, Users } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import axiosInstance from '../../api/axiosInstance';
|
||||
import Searchs from '../../components/Searchs';
|
||||
import Pagination from '../Pagination';
|
||||
import Searchs from '../Searchs';
|
||||
|
||||
const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
|
||||
const client = useQueryClient();
|
||||
@@ -24,7 +24,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
|
||||
axiosInstance.get('/clients/list', {
|
||||
params: {
|
||||
page: currentPage,
|
||||
sort: 'id',
|
||||
sort: 'active',
|
||||
clientName: searchTerm,
|
||||
direction: 'desc',
|
||||
},
|
||||
@@ -44,7 +44,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
|
||||
};
|
||||
|
||||
const { mutate: deleteClient, isPending: deleteLoad } = useMutation({
|
||||
mutationFn: (id: number) => axiosInstance.delete(`/users/delete/${id}`),
|
||||
mutationFn: (id) => axiosInstance.delete(`/users/delete/${id}`),
|
||||
mutationKey: ['clientsDelete'],
|
||||
onSuccess: () => {
|
||||
client.invalidateQueries({ queryKey: ['clients'] });
|
||||
@@ -57,7 +57,7 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
|
||||
},
|
||||
});
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
const handlePageChange = (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">PNFL</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>
|
||||
</thead>
|
||||
<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.pinfl}</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') && (
|
||||
<td className="p-4">
|
||||
<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" />
|
||||
<span className="hidden sm:inline">Tahrirlash</span>
|
||||
</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" />
|
||||
<span className="hidden sm:inline">O'chirish</span>
|
||||
</button>
|
||||
@@ -116,7 +137,13 @@ const ClientList = ({ alert, setAlert, setClients, setCargoId }) => {
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
@@ -80,7 +80,7 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
|
||||
const { data: client, refetch: clientRef } = useQuery({
|
||||
queryKey: ['client_one'],
|
||||
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({
|
||||
@@ -102,23 +102,24 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
|
||||
phone: c.phone,
|
||||
pinfl: c.pinfl,
|
||||
});
|
||||
setActive(c.active);
|
||||
}
|
||||
clientRef();
|
||||
refetch();
|
||||
}, [cargoId, client, refetch, clientRef]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (currentPassport) {
|
||||
// setFormData((prev) => ({
|
||||
// ...prev,
|
||||
// fullName: currentPassport.fullName || '',
|
||||
// passportSeries: currentPassport.passportSeries || '',
|
||||
// pinfl: currentPassport.passportPin || '',
|
||||
// dateOfBirth: formatDate(currentPassport.birthDate),
|
||||
// }));
|
||||
// setActive(currentPassport.active);
|
||||
// }
|
||||
// }, [currentPassport]);
|
||||
useEffect(() => {
|
||||
if (currentPassport) {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
fullName: currentPassport.fullName || '',
|
||||
passportSeries: currentPassport.passportSeries || '',
|
||||
pinfl: currentPassport.passportPin || '',
|
||||
dateOfBirth: formatDate(currentPassport.birthDate),
|
||||
}));
|
||||
setActive(currentPassport.active);
|
||||
}
|
||||
}, [currentPassport]);
|
||||
|
||||
const { mutate: createMutate } = useMutation({
|
||||
mutationFn: async (params: any) => {
|
||||
@@ -202,6 +203,8 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
|
||||
setAlert({ type: 'success', message: "Passport ma'lumotlari yangilandi." });
|
||||
};
|
||||
|
||||
console.log(active);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(userRole === 'ADMIN' || userRole === 'SUPER_ADMIN') && (
|
||||
@@ -211,7 +214,15 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
|
||||
{clients ? 'Mijozni tahrirlash' : "Yangi mijoz qo'shish"}
|
||||
</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}>
|
||||
<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) => (
|
||||
<div key={field.name}>
|
||||
<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>
|
||||
))}
|
||||
|
||||
{clients && currentPassport && (
|
||||
<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="absolute left-1 top-1 w-4 h-4 bg-white rounded-full transition-transform duration-300 peer-checked:translate-x-5"></div>
|
||||
</label>
|
||||
@@ -239,22 +261,42 @@ const CreateClient = ({ clients, setAlert, cargoId }: CreateClientProps) => {
|
||||
</div>
|
||||
|
||||
<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"}
|
||||
</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
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
{tagetIamge && opneModal && <ModalIamge image={tagetIamge} setTargetImage={setTargetImage} setOpenModal={setOpenModal} />}
|
||||
{tagetIamge && opneModal && (
|
||||
<ModalIamge
|
||||
image={tagetIamge}
|
||||
setTargetImage={setTargetImage}
|
||||
setOpenModal={setOpenModal}
|
||||
/>
|
||||
)}
|
||||
</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 (
|
||||
<div className="w-full h-screen bg-black/60 fixed top-0 bottom-0 left-0 z-50 overflow-hidden">
|
||||
<button
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
'use client';
|
||||
|
||||
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 axiosInstance from '../../api/axiosInstance';
|
||||
import { cn } from '../../lib/utils';
|
||||
@@ -71,7 +81,9 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
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];
|
||||
|
||||
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="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>
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -90,7 +105,10 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
<div className="flex flex-col items-center justify-center py-12">
|
||||
<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>
|
||||
<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
|
||||
</button>
|
||||
</div>
|
||||
@@ -125,7 +143,14 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<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" />
|
||||
<span className="font-medium">{currentStatus.label}</span>
|
||||
</button>
|
||||
@@ -162,11 +187,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
<button
|
||||
className={cn(
|
||||
'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" />
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -183,16 +218,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
{data.clickTransactions.map((transaction, index) => (
|
||||
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="font-medium text-lg text-gray-900">ID: {transaction.transactionId}</span>
|
||||
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg ${getStatusColor(transaction.status)}`}>
|
||||
{getStatusIcon(transaction.status)}
|
||||
<span className="font-medium text-lg text-gray-900">
|
||||
ID: {transaction.transactionId}
|
||||
</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg`}
|
||||
>
|
||||
{transaction.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<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>
|
||||
<span className="font-medium text-lg text-gray-600">Provayder:</span>
|
||||
@@ -204,13 +244,19 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
</div>
|
||||
{transaction.completeTime && (
|
||||
<div>
|
||||
<span className="font-medium text-lg text-gray-600">Tugallangan:</span>
|
||||
<p className="text-gray-900">{formatDate(transaction.completeTime)}</p>
|
||||
<span className="font-medium text-lg text-gray-600">
|
||||
Tugallangan:
|
||||
</span>
|
||||
<p className="text-gray-900">
|
||||
{formatDate(transaction.completeTime)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{transaction.cancelTime && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -239,16 +285,21 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
{data.paymeTransactions.map((transaction, index) => (
|
||||
<div key={index} className="border border-gray-200 rounded-lg p-4 bg-white">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="font-medium text-lg text-gray-900">ID: {transaction.transactionId}</span>
|
||||
<span className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg ${getStatusColor(transaction.status)}`}>
|
||||
{getStatusIcon(transaction.status)}
|
||||
<span className="font-medium text-lg text-gray-900">
|
||||
ID: {transaction.transactionId}
|
||||
</span>
|
||||
<span
|
||||
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full font-medium text-lg`}
|
||||
>
|
||||
{transaction.status}
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div>
|
||||
<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>
|
||||
<span className="font-medium text-lg text-gray-600">Provayder:</span>
|
||||
@@ -260,13 +311,19 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
</div>
|
||||
{transaction.completeTime && (
|
||||
<div>
|
||||
<span className="font-medium text-lg text-gray-600">Tugallangan:</span>
|
||||
<p className="text-gray-900">{formatDate(transaction.completeTime)}</p>
|
||||
<span className="font-medium text-lg text-gray-600">
|
||||
Tugallangan:
|
||||
</span>
|
||||
<p className="text-gray-900">
|
||||
{formatDate(transaction.completeTime)}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
{transaction.cancelTime && (
|
||||
<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>
|
||||
</div>
|
||||
)}
|
||||
@@ -285,7 +342,11 @@ const ModalPayment = ({ isOpen, onClose, packetId }) => {
|
||||
</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">
|
||||
<Clock className="w-12 h-12 text-gray-400 mx-auto mb-3" />
|
||||
<p className="text-gray-500">Hozircha tranzaksiyalar mavjud emas</p>
|
||||
|
||||
@@ -40,18 +40,14 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
|
||||
const [position, setPosition] = useState({ top: 0, left: 0, width: 0 });
|
||||
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 handleSelect = (statusValue) => {
|
||||
if (statusValue === 'PAID' && (value === 'UNPAID' || value === 'PENDING')) {
|
||||
// Faqat PAYED holatiga o'tkazish
|
||||
if (statusValue === 'PAYED' && value !== '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);
|
||||
};
|
||||
@@ -88,17 +84,18 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
|
||||
<CurrentIcon className="h-4 w-4" />
|
||||
<span className="font-medium">{currentStatus.label}</span>
|
||||
</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>
|
||||
|
||||
{/* Options (portal) */}
|
||||
{isOpen &&
|
||||
createPortal(
|
||||
<>
|
||||
{/* Backdrop */}
|
||||
<div className="fixed inset-0 z-40" onClick={() => setIsOpen(false)} />
|
||||
|
||||
{/* Dropdown */}
|
||||
<div
|
||||
className="absolute bg-white border border-slate-200 rounded-lg shadow-lg z-50 overflow-hidden"
|
||||
style={{
|
||||
@@ -111,17 +108,24 @@ const PaymentStatusSelector = ({ value, onChange, disabled = false }) => {
|
||||
{paymentStatuses.map((status) => {
|
||||
const StatusIcon = status.icon;
|
||||
const isSelected = status.value === value;
|
||||
const isDisabled = status.value !== 'PAID' || value === 'PAID';
|
||||
|
||||
return (
|
||||
<button
|
||||
key={status.value}
|
||||
type="button"
|
||||
onClick={() => !isDisabled && handleSelect(status.value)}
|
||||
disabled={isDisabled}
|
||||
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')}
|
||||
onClick={() => handleSelect(status.value)}
|
||||
className={cn(
|
||||
'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)} />
|
||||
</div>
|
||||
<span className={cn('font-medium', status.color)}>{status.label}</span>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { QrCode, Search, Users } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import axiosInstance from '../../api/axiosInstance';
|
||||
import Pagination from '../Pagination';
|
||||
import ModalPayment from './ModalPayment';
|
||||
@@ -17,15 +17,17 @@ const PaymentsList = ({ setAlert }) => {
|
||||
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||
const [scannedResult, setScannedResult] = useState(null);
|
||||
const [packetid, setPacketId] = useState();
|
||||
const [userRole, setUserRole] = useState('');
|
||||
|
||||
const userRole = (() => {
|
||||
try {
|
||||
useEffect(() => {
|
||||
const storedUser = localStorage.getItem('user');
|
||||
return storedUser ? JSON.parse(storedUser).user.role : null;
|
||||
} catch {
|
||||
return null;
|
||||
if (storedUser) {
|
||||
const parsed = JSON.parse(storedUser);
|
||||
setUserRole(parsed.user?.role || null);
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
console.log(userRole);
|
||||
|
||||
const { data, isLoading, refetch } = useQuery({
|
||||
queryKey: ['payments_list', currentPage, searchTerm, cargoType],
|
||||
@@ -36,6 +38,7 @@ const PaymentsList = ({ setAlert }) => {
|
||||
cargoType,
|
||||
size: 30,
|
||||
search: searchTerm,
|
||||
sort: 'paymentStatus',
|
||||
},
|
||||
}),
|
||||
select(data) {
|
||||
@@ -86,7 +89,11 @@ const PaymentsList = ({ setAlert }) => {
|
||||
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="mb-4 w-[90%]">
|
||||
@@ -97,7 +104,7 @@ const PaymentsList = ({ setAlert }) => {
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
placeholder={"Cargo Ids, Partiya nomi, Paket nomi bo'yicha qidirish"}
|
||||
placeholder={"Cargo Ids, Paket nomi bo'yicha qidirish"}
|
||||
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]"
|
||||
@@ -106,7 +113,11 @@ const PaymentsList = ({ setAlert }) => {
|
||||
</div>
|
||||
</div>
|
||||
<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="AVIA">AVIA</option>
|
||||
<option value="AUTO">AUTO</option>
|
||||
@@ -115,7 +126,10 @@ const PaymentsList = ({ setAlert }) => {
|
||||
</div>
|
||||
|
||||
<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" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -162,10 +176,20 @@ const PaymentsList = ({ setAlert }) => {
|
||||
<td className="p-4">{client.cargoId}</td>
|
||||
<td className="p-4">{client.partyName}</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') && (
|
||||
<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 className="p-4">
|
||||
@@ -187,7 +211,13 @@ const PaymentsList = ({ setAlert }) => {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user