category service

This commit is contained in:
hamid zarghami
2025-01-26 15:45:46 +03:30
parent bfb9f3cbcb
commit 22482aa20a
34 changed files with 1146 additions and 204 deletions
+4 -4
View File
@@ -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
}
+24 -2
View File
@@ -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>
)
+19
View File
@@ -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
+99
View File
@@ -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;
+4 -3
View File
@@ -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>