@@ -12,27 +12,36 @@ type Props = {
|
||||
XOR<{ children: ReactNode }, { label: string }>;
|
||||
|
||||
const Button: FC<Props> = memo((props: Props) => {
|
||||
const {
|
||||
className,
|
||||
disabled,
|
||||
isLoading,
|
||||
percentage,
|
||||
label,
|
||||
children,
|
||||
...buttonProps
|
||||
} = props
|
||||
|
||||
const buttonClass = clx(
|
||||
'flex rounded-xl items-center justify-center text-center h-10 text-sm bg-primary w-full',
|
||||
props.disabled && 'cursor-not-allowed opacity-60',
|
||||
props.className
|
||||
disabled && 'cursor-not-allowed opacity-60',
|
||||
className
|
||||
);
|
||||
|
||||
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'>
|
||||
{
|
||||
!props.percentage ?
|
||||
!percentage ?
|
||||
<MoonLoader size={20} color='#000' />
|
||||
:
|
||||
<span>{props.percentage}%</span>
|
||||
<span>{percentage}%</span>
|
||||
}
|
||||
</div>
|
||||
:
|
||||
props.label || props.children
|
||||
label || children
|
||||
}
|
||||
</button>
|
||||
)
|
||||
|
||||
+21
-11
@@ -17,27 +17,37 @@ type Props = {
|
||||
} & SelectHTMLAttributes<HTMLSelectElement>
|
||||
|
||||
const Select: FC<Props> = (props: Props) => {
|
||||
const {
|
||||
className,
|
||||
items,
|
||||
error_text,
|
||||
placeholder,
|
||||
label,
|
||||
readOnly,
|
||||
...selectProps
|
||||
} = props
|
||||
|
||||
return (
|
||||
<div className='w-full'>
|
||||
{
|
||||
props.label &&
|
||||
label &&
|
||||
<label className='text-sm text-primary-content'>
|
||||
{props.label}
|
||||
{label}
|
||||
</label>
|
||||
}
|
||||
<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',
|
||||
props.readOnly && 'bg-muted border-0 text-muted-foreground',
|
||||
props.className,
|
||||
props.label && 'mt-1'
|
||||
readOnly && 'bg-muted border-0 text-muted-foreground',
|
||||
className,
|
||||
label && 'mt-1'
|
||||
)}>
|
||||
{
|
||||
props.placeholder &&
|
||||
<option value="" disabled selected>{props.placeholder}</option>
|
||||
placeholder &&
|
||||
<option value="" disabled>{placeholder}</option>
|
||||
}
|
||||
{
|
||||
props.items?.map((item) => {
|
||||
items?.map((item) => {
|
||||
return (
|
||||
<option key={item.value} value={item.value} disabled={item.disabled}>
|
||||
{item.label}
|
||||
@@ -51,9 +61,9 @@ const Select: FC<Props> = (props: Props) => {
|
||||
/>
|
||||
</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'>
|
||||
{props.error_text}
|
||||
{error_text}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
|
||||
@@ -54,12 +54,24 @@ const getCategoriesAtLevel = (
|
||||
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 { error_text, onProductSelect, value, onChange, ...rest } = props
|
||||
const [selectedPath, setSelectedPath] = useState<string[]>([])
|
||||
|
||||
const { data: categoriesData } = useGetCategories()
|
||||
const categories = categoriesData?.data ?? []
|
||||
const categories = useMemo(
|
||||
() => extractList<CategoryType>(categoriesData),
|
||||
[categoriesData],
|
||||
)
|
||||
|
||||
const levelsToShow = useMemo(() => {
|
||||
const levels = [0]
|
||||
@@ -75,11 +87,7 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
}, [categories, selectedPath])
|
||||
|
||||
const activeCategoryId = selectedPath.at(-1) ?? ''
|
||||
const activeCategory = activeCategoryId
|
||||
? findCategoryById(categories, activeCategoryId)
|
||||
: null
|
||||
const isLeafCategory = !!activeCategory && !(activeCategory.children?.length)
|
||||
const canLoadProducts = isLeafCategory
|
||||
const canLoadProducts = !!activeCategoryId
|
||||
|
||||
const { data: productsData } = useGetProducts(activeCategoryId || undefined, {
|
||||
enabled: canLoadProducts,
|
||||
@@ -90,23 +98,28 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
enabled: needsResolve,
|
||||
})
|
||||
|
||||
const productItems = useMemo(
|
||||
() => (productsData?.data ?? []).map((product) => ({
|
||||
label: product.title,
|
||||
value: product.id,
|
||||
})),
|
||||
const products = useMemo(
|
||||
() => extractList<ProductType>(productsData),
|
||||
[productsData],
|
||||
)
|
||||
|
||||
const resolveProducts = useMemo(
|
||||
() => extractList<ProductType>(resolveProductsData),
|
||||
[resolveProductsData],
|
||||
)
|
||||
|
||||
const productItems = useMemo(
|
||||
() => products.map((product) => ({
|
||||
label: product.title,
|
||||
value: product.id,
|
||||
})),
|
||||
[products],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
setSelectedPath([])
|
||||
return
|
||||
}
|
||||
if (!value || selectedPath.length || !categories.length) return
|
||||
|
||||
if (selectedPath.length || !categories.length) return
|
||||
|
||||
const product = resolveProductsData?.data?.find((item) => item.id === value)
|
||||
const product = resolveProducts.find((item) => item.id === value)
|
||||
if (!product?.category) return
|
||||
|
||||
const categoryId = typeof product.category === 'string'
|
||||
@@ -117,7 +130,7 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
if (!path) return
|
||||
|
||||
setSelectedPath(path)
|
||||
}, [value, resolveProductsData, categories, selectedPath.length])
|
||||
}, [value, resolveProducts, categories, selectedPath.length])
|
||||
|
||||
useEffect(() => {
|
||||
if (!value) {
|
||||
@@ -125,11 +138,11 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
return
|
||||
}
|
||||
|
||||
const product = productsData?.data?.find((item) => item.id === value)
|
||||
?? resolveProductsData?.data?.find((item) => item.id === value)
|
||||
const product = products.find((item) => item.id === value)
|
||||
?? resolveProducts.find((item) => item.id === value)
|
||||
|
||||
onProductSelect?.(product)
|
||||
}, [value, productsData, resolveProductsData, onProductSelect])
|
||||
}, [value, products, resolveProducts, onProductSelect])
|
||||
|
||||
const emitProductChange = (nextValue: string) => {
|
||||
onChange?.({
|
||||
@@ -145,7 +158,7 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
const handleProductChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -175,7 +188,7 @@ const ProductsSelect: FC<Props> = (props) => {
|
||||
value={value}
|
||||
onChange={handleProductChange}
|
||||
error_text={error_text}
|
||||
disabled={!canLoadProducts}
|
||||
disabled={!activeCategoryId}
|
||||
{...rest}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import axios from "@/config/axios";
|
||||
import type {
|
||||
AddTicketType,
|
||||
AttributeResponseType,
|
||||
CategoriesResponseType,
|
||||
CategoryType,
|
||||
MyRequestsResponseType,
|
||||
RequestDetailResponseType,
|
||||
RequestType,
|
||||
@@ -11,7 +11,7 @@ import type {
|
||||
} from "../type/Types";
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user