cat
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-09 17:35:24 +03:30
parent adad81467c
commit 1e6504771a
4 changed files with 76 additions and 44 deletions
+16 -7
View File
@@ -12,27 +12,36 @@ type Props = {
XOR<{ children: ReactNode }, { label: string }>; XOR<{ children: ReactNode }, { label: string }>;
const Button: FC<Props> = memo((props: Props) => { const Button: FC<Props> = memo((props: Props) => {
const {
className,
disabled,
isLoading,
percentage,
label,
children,
...buttonProps
} = props
const buttonClass = clx( const buttonClass = clx(
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary w-full', 'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary w-full',
props.disabled && 'cursor-not-allowed opacity-60', disabled && 'cursor-not-allowed opacity-60',
props.className className
); );
return ( return (
<button {...props} className={`${buttonClass} ${props.className}`} disabled={props.disabled || props.isLoading}> <button {...buttonProps} className={`${buttonClass} ${className}`} disabled={disabled || isLoading}>
{ {
props.isLoading ? isLoading ?
<div className='flex gap-2 items-center'> <div className='flex gap-2 items-center'>
{ {
!props.percentage ? !percentage ?
<MoonLoader size={20} color='#000' /> <MoonLoader size={20} color='#000' />
: :
<span>{props.percentage}%</span> <span>{percentage}%</span>
} }
</div> </div>
: :
props.label || props.children label || children
} }
</button> </button>
) )
+21 -11
View File
@@ -17,27 +17,37 @@ type Props = {
} & SelectHTMLAttributes<HTMLSelectElement> } & SelectHTMLAttributes<HTMLSelectElement>
const Select: FC<Props> = (props: Props) => { const Select: FC<Props> = (props: Props) => {
const {
className,
items,
error_text,
placeholder,
label,
readOnly,
...selectProps
} = props
return ( return (
<div className='w-full'> <div className='w-full'>
{ {
props.label && label &&
<label className='text-sm text-primary-content'> <label className='text-sm text-primary-content'>
{props.label} {label}
</label> </label>
} }
<div className='relative'> <div className='relative'>
<select {...props} className={clx( <select {...selectProps} className={clx(
'w-full bg-white border border-border input-surface relative block appearance-none px-2.5 h-10 text-sm rounded-[10px] transition-colors', 'w-full bg-white border border-border input-surface relative block appearance-none px-2.5 h-10 text-sm rounded-[10px] transition-colors',
props.readOnly && 'bg-muted border-0 text-muted-foreground', readOnly && 'bg-muted border-0 text-muted-foreground',
props.className, className,
props.label && 'mt-1' label && 'mt-1'
)}> )}>
{ {
props.placeholder && placeholder &&
<option value="" disabled selected>{props.placeholder}</option> <option value="" disabled>{placeholder}</option>
} }
{ {
props.items?.map((item) => { items?.map((item) => {
return ( return (
<option key={item.value} value={item.value} disabled={item.disabled}> <option key={item.value} value={item.value} disabled={item.disabled}>
{item.label} {item.label}
@@ -51,9 +61,9 @@ const Select: FC<Props> = (props: Props) => {
/> />
</div> </div>
{ {
props.error_text && props.error_text !== '' ? error_text && error_text !== '' ?
<div className='text-xs text-right text-red-600 mt-2 mr-2 font-medium'> <div className='text-xs text-right text-red-600 mt-2 mr-2 font-medium'>
{props.error_text} {error_text}
</div> </div>
: null : null
} }
+37 -24
View File
@@ -54,12 +54,24 @@ const getCategoriesAtLevel = (
return parent?.children ?? [] return parent?.children ?? []
} }
const extractList = <T,>(payload: unknown): T[] => {
if (Array.isArray(payload)) return payload
if (payload && typeof payload === 'object' && 'data' in payload) {
const maybeData = (payload as { data?: unknown }).data
return Array.isArray(maybeData) ? (maybeData as T[]) : []
}
return []
}
const ProductsSelect: FC<Props> = (props) => { const ProductsSelect: FC<Props> = (props) => {
const { error_text, onProductSelect, value, onChange, ...rest } = props const { error_text, onProductSelect, value, onChange, ...rest } = props
const [selectedPath, setSelectedPath] = useState<string[]>([]) const [selectedPath, setSelectedPath] = useState<string[]>([])
const { data: categoriesData } = useGetCategories() const { data: categoriesData } = useGetCategories()
const categories = categoriesData?.data ?? [] const categories = useMemo(
() => extractList<CategoryType>(categoriesData),
[categoriesData],
)
const levelsToShow = useMemo(() => { const levelsToShow = useMemo(() => {
const levels = [0] const levels = [0]
@@ -75,11 +87,7 @@ const ProductsSelect: FC<Props> = (props) => {
}, [categories, selectedPath]) }, [categories, selectedPath])
const activeCategoryId = selectedPath.at(-1) ?? '' const activeCategoryId = selectedPath.at(-1) ?? ''
const activeCategory = activeCategoryId const canLoadProducts = !!activeCategoryId
? findCategoryById(categories, activeCategoryId)
: null
const isLeafCategory = !!activeCategory && !(activeCategory.children?.length)
const canLoadProducts = isLeafCategory
const { data: productsData } = useGetProducts(activeCategoryId || undefined, { const { data: productsData } = useGetProducts(activeCategoryId || undefined, {
enabled: canLoadProducts, enabled: canLoadProducts,
@@ -90,23 +98,28 @@ const ProductsSelect: FC<Props> = (props) => {
enabled: needsResolve, enabled: needsResolve,
}) })
const productItems = useMemo( const products = useMemo(
() => (productsData?.data ?? []).map((product) => ({ () => extractList<ProductType>(productsData),
label: product.title,
value: product.id,
})),
[productsData], [productsData],
) )
const resolveProducts = useMemo(
() => extractList<ProductType>(resolveProductsData),
[resolveProductsData],
)
const productItems = useMemo(
() => products.map((product) => ({
label: product.title,
value: product.id,
})),
[products],
)
useEffect(() => { useEffect(() => {
if (!value) { if (!value || selectedPath.length || !categories.length) return
setSelectedPath([])
return
}
if (selectedPath.length || !categories.length) return const product = resolveProducts.find((item) => item.id === value)
const product = resolveProductsData?.data?.find((item) => item.id === value)
if (!product?.category) return if (!product?.category) return
const categoryId = typeof product.category === 'string' const categoryId = typeof product.category === 'string'
@@ -117,7 +130,7 @@ const ProductsSelect: FC<Props> = (props) => {
if (!path) return if (!path) return
setSelectedPath(path) setSelectedPath(path)
}, [value, resolveProductsData, categories, selectedPath.length]) }, [value, resolveProducts, categories, selectedPath.length])
useEffect(() => { useEffect(() => {
if (!value) { if (!value) {
@@ -125,11 +138,11 @@ const ProductsSelect: FC<Props> = (props) => {
return return
} }
const product = productsData?.data?.find((item) => item.id === value) const product = products.find((item) => item.id === value)
?? resolveProductsData?.data?.find((item) => item.id === value) ?? resolveProducts.find((item) => item.id === value)
onProductSelect?.(product) onProductSelect?.(product)
}, [value, productsData, resolveProductsData, onProductSelect]) }, [value, products, resolveProducts, onProductSelect])
const emitProductChange = (nextValue: string) => { const emitProductChange = (nextValue: string) => {
onChange?.({ onChange?.({
@@ -145,7 +158,7 @@ const ProductsSelect: FC<Props> = (props) => {
const handleProductChange = (e: ChangeEvent<HTMLSelectElement>) => { const handleProductChange = (e: ChangeEvent<HTMLSelectElement>) => {
onChange?.(e) onChange?.(e)
const product = productsData?.data?.find((item) => item.id === e.target.value) const product = products.find((item) => item.id === e.target.value)
onProductSelect?.(product) onProductSelect?.(product)
} }
@@ -175,7 +188,7 @@ const ProductsSelect: FC<Props> = (props) => {
value={value} value={value}
onChange={handleProductChange} onChange={handleProductChange}
error_text={error_text} error_text={error_text}
disabled={!canLoadProducts} disabled={!activeCategoryId}
{...rest} {...rest}
/> />
</div> </div>
+2 -2
View File
@@ -2,7 +2,7 @@ import axios from "@/config/axios";
import type { import type {
AddTicketType, AddTicketType,
AttributeResponseType, AttributeResponseType,
CategoriesResponseType, CategoryType,
MyRequestsResponseType, MyRequestsResponseType,
RequestDetailResponseType, RequestDetailResponseType,
RequestType, RequestType,
@@ -11,7 +11,7 @@ import type {
} from "../type/Types"; } from "../type/Types";
export const getCategories = async () => { export const getCategories = async () => {
const { data } = await axios.get<CategoriesResponseType>(`/public/categories`); const { data } = await axios.get<CategoryType[]>(`/public/category/tree?isActive=true`);
return data; return data;
}; };