category service
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { ButtonHTMLAttributes, FC, memo, ReactNode } from 'react'
|
||||
import ReactLoading from 'react-loading'
|
||||
import MoonLoader from "react-spinners/MoonLoader"
|
||||
import { XOR } from '../helpers/types'
|
||||
import { clx } from '../helpers/utils'
|
||||
|
||||
@@ -7,7 +7,7 @@ type Props = {
|
||||
className?: string;
|
||||
isLoading?: boolean,
|
||||
} & ButtonHTMLAttributes<HTMLButtonElement> &
|
||||
XOR<{ children: ReactNode }, { label: string, isLoading?: boolean }>;
|
||||
XOR<{ children: ReactNode }, { label: string }>;
|
||||
|
||||
const Button: FC<Props> = memo((props: Props) => {
|
||||
|
||||
@@ -18,10 +18,10 @@ const Button: FC<Props> = memo((props: Props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<button {...props} className={`${buttonClass} ${props.className}`} >
|
||||
<button disabled={props.isLoading} {...props} className={`${buttonClass} ${props.className}`} >
|
||||
{
|
||||
props.isLoading ?
|
||||
<ReactLoading type='spin' color={'white'} width={20} height={20} />
|
||||
<MoonLoader color="white" size={16} />
|
||||
:
|
||||
props.label || props.children
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { FC, InputHTMLAttributes, useState } from 'react'
|
||||
import { FC, InputHTMLAttributes, useEffect, useState } from 'react'
|
||||
import { clx } from '../helpers/utils';
|
||||
import EyeIcon from '../assets/images/eye.svg'
|
||||
import { SearchNormal } from 'iconsax-react';
|
||||
import Error from './Error';
|
||||
|
||||
type Variant = "floating_outlined" | "primary" | "search";
|
||||
|
||||
@@ -14,11 +15,13 @@ type Props = {
|
||||
unit?: string;
|
||||
seprator?: boolean;
|
||||
isNotRequired?: boolean;
|
||||
onChangeSearchFinal?: (value: string) => void;
|
||||
} & InputHTMLAttributes<HTMLInputElement>
|
||||
|
||||
const Input: FC<Props> = (props: Props) => {
|
||||
|
||||
const [showPassword, setShowPassword] = useState<boolean>(false)
|
||||
const [search, setSearch] = useState<string>('')
|
||||
|
||||
const inputClass = clx(
|
||||
'w-full h-10 text-black block px-4 text-xs rounded-xl border border-border',
|
||||
@@ -27,6 +30,15 @@ const Input: FC<Props> = (props: Props) => {
|
||||
props.className
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.variant === 'search' && props.onChangeSearchFinal) {
|
||||
const timeout = setTimeout(() => {
|
||||
props.onChangeSearchFinal?.(search)
|
||||
}, 1000)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [search, props])
|
||||
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
@@ -35,7 +47,10 @@ const Input: FC<Props> = (props: Props) => {
|
||||
</label>
|
||||
|
||||
<div className='w-full relative mt-1'>
|
||||
<input {...props} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
||||
<input onChange={(e) => {
|
||||
setSearch(e.target.value)
|
||||
props.onChange?.(e)
|
||||
}} {...props} type={props.type === 'password' && showPassword ? 'text' : props.type === 'password' ? 'password' : undefined} className={inputClass} />
|
||||
|
||||
{
|
||||
props.type === 'password' &&
|
||||
@@ -46,6 +61,13 @@ const Input: FC<Props> = (props: Props) => {
|
||||
props.variant === 'search' &&
|
||||
<SearchNormal size={20} color='#8C90A3' className='absolute top-0 w-5 bottom-0 my-auto right-3' />
|
||||
}
|
||||
|
||||
{
|
||||
props.error_text &&
|
||||
<Error
|
||||
errorText={props.error_text}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { FC } from 'react'
|
||||
import Logo from '../assets/images/logo_orig.svg'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const PageLoading: FC = () => {
|
||||
|
||||
const { t } = useTranslation('global')
|
||||
|
||||
return (
|
||||
<div className='flex bg-white w-fit mx-auto flex-col gap-4 items-center'>
|
||||
<img src={Logo} alt='logo' className='w-28' />
|
||||
<div className='text-xs text-gray-700'>
|
||||
{t('loading')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageLoading
|
||||
@@ -0,0 +1,99 @@
|
||||
import React from "react";
|
||||
|
||||
interface PaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
}
|
||||
|
||||
const Pagination: React.FC<PaginationProps> = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const getPageNumbers = () => {
|
||||
const pageNumbers: (number | string)[] = [];
|
||||
const maxVisiblePages = 5; // تعداد حداکثری صفحات قابل مشاهده
|
||||
|
||||
if (totalPages <= maxVisiblePages) {
|
||||
for (let i = 1; i <= totalPages; i++) {
|
||||
pageNumbers.push(i);
|
||||
}
|
||||
} else {
|
||||
if (currentPage > 3) {
|
||||
pageNumbers.push(1);
|
||||
if (currentPage > 4) {
|
||||
pageNumbers.push("...");
|
||||
}
|
||||
}
|
||||
|
||||
const start = Math.max(2, currentPage - 1);
|
||||
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||
|
||||
for (let i = start; i <= end; i++) {
|
||||
pageNumbers.push(i);
|
||||
}
|
||||
|
||||
if (currentPage < totalPages - 2) {
|
||||
if (currentPage < totalPages - 3) {
|
||||
pageNumbers.push("...");
|
||||
}
|
||||
pageNumbers.push(totalPages);
|
||||
}
|
||||
}
|
||||
|
||||
return pageNumbers;
|
||||
};
|
||||
|
||||
const pageNumbers = getPageNumbers();
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 text-xs justify-center items-center space-x-2 mt-4">
|
||||
{/* دکمه قبلی */}
|
||||
{/* <button
|
||||
className={`px-3 py-1 rounded-md ${currentPage === 1
|
||||
? "text-gray-400 cursor-not-allowed"
|
||||
: "text-blue-600 hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => currentPage > 1 && onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
>
|
||||
قبلی
|
||||
</button> */}
|
||||
|
||||
{/* شماره صفحات */}
|
||||
{pageNumbers.map((page, index) =>
|
||||
typeof page === "number" ? (
|
||||
<button
|
||||
key={index}
|
||||
className={`size-8 rounded-md ${currentPage === page
|
||||
? "bg-primary text-white"
|
||||
: "text-primary bg-[#EAECF5] hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => onPageChange(page)}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
) : (
|
||||
<span key={index} className="px-3 py-1 text-gray-500">
|
||||
{page}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* دکمه بعدی */}
|
||||
{/* <button
|
||||
className={`px-3 py-1 rounded-md ${currentPage === totalPages
|
||||
? "text-gray-400 cursor-not-allowed"
|
||||
: "text-blue-600 hover:bg-gray-100"
|
||||
}`}
|
||||
onClick={() => currentPage < totalPages && onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
>
|
||||
بعدی
|
||||
</button> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FC, SelectHTMLAttributes } from 'react'
|
||||
import { clx } from '../helpers/utils'
|
||||
import { ArrowDown2 } from 'iconsax-react'
|
||||
import Error from './Error'
|
||||
|
||||
export type ItemsSelectType = {
|
||||
value: string,
|
||||
@@ -48,9 +49,9 @@ const Select: FC<Props> = (props: Props) => {
|
||||
)} />
|
||||
{
|
||||
props.error_text && props.error_text !== '' ?
|
||||
<div className='text-xs text-right text-red-600 mt-2 mr-2 font-medium'>
|
||||
{props.error_text}
|
||||
</div>
|
||||
<Error
|
||||
errorText={props.error_text}
|
||||
/>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user