admin panel
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { FC, useEffect, useState, ChangeEvent } from 'react';
|
||||
import { Filter, CloseCircle } from 'iconsax-react';
|
||||
import DatePicker from './DatePicker';
|
||||
import Input from './Input';
|
||||
import Select, { ItemsSelectType } from './Select';
|
||||
|
||||
// تعریف نوع داده برای فیلدهای مختلف
|
||||
export type DateFieldType = {
|
||||
type: 'date';
|
||||
name: string;
|
||||
placeholder: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type SelectFieldType = {
|
||||
type: 'select';
|
||||
name: string;
|
||||
placeholder: string;
|
||||
options: ItemsSelectType[];
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type InputFieldType = {
|
||||
type: 'input';
|
||||
name: string;
|
||||
placeholder: string;
|
||||
defaultValue?: string;
|
||||
};
|
||||
|
||||
export type FieldType = DateFieldType | SelectFieldType | InputFieldType;
|
||||
|
||||
// تعریف نوع داده برای مقادیر فیلترها
|
||||
export type FilterValues = Record<string, string | null>;
|
||||
|
||||
interface FiltersProps {
|
||||
fields: FieldType[];
|
||||
onChange: (filters: FilterValues) => void;
|
||||
initialValues?: FilterValues;
|
||||
className?: string;
|
||||
fieldClassName?: string;
|
||||
searchField?: string; // نام فیلد جستجو که باید در انتها نمایش داده شود
|
||||
}
|
||||
|
||||
const Filters: FC<FiltersProps> = ({
|
||||
fields,
|
||||
onChange,
|
||||
initialValues = {},
|
||||
className = "mt-6 md:mt-8 xl:mt-10 flex flex-col md:flex-row justify-between items-start md:items-center gap-4",
|
||||
fieldClassName = "flex flex-col sm:flex-row gap-3 md:gap-4 w-full md:w-auto",
|
||||
searchField = "search" // پیشفرض فیلد سرچ با نام search
|
||||
}) => {
|
||||
const [filters, setFilters] = useState<FilterValues>({});
|
||||
const [isFiltersOpen, setIsFiltersOpen] = useState(false);
|
||||
|
||||
// تنظیم مقادیر اولیه
|
||||
useEffect(() => {
|
||||
if (Object.keys(initialValues).length > 0) {
|
||||
setFilters(initialValues);
|
||||
} else {
|
||||
// تنظیم مقادیر پیشفرض از فیلدها
|
||||
const defaultValues: FilterValues = {};
|
||||
fields.forEach(field => {
|
||||
if ('defaultValue' in field && field.defaultValue !== undefined) {
|
||||
defaultValues[field.name] = field.defaultValue;
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(defaultValues).length > 0) {
|
||||
setFilters(defaultValues);
|
||||
onChange(defaultValues);
|
||||
}
|
||||
}
|
||||
}, [fields, initialValues, onChange]);
|
||||
|
||||
const handleChange = (name: string, value: string | null) => {
|
||||
const newFilters = { ...filters, [name]: value };
|
||||
setFilters(newFilters);
|
||||
onChange(newFilters);
|
||||
};
|
||||
|
||||
const handleInputChange = (name: string, event: ChangeEvent<HTMLInputElement>) => {
|
||||
handleChange(name, event.target.value);
|
||||
};
|
||||
|
||||
const renderField = (field: FieldType) => {
|
||||
const currentValue = filters[field.name];
|
||||
|
||||
switch (field.type) {
|
||||
case 'date':
|
||||
return (
|
||||
<DatePicker
|
||||
key={field.name}
|
||||
placeholder={field.placeholder}
|
||||
onChange={(value) => handleChange(field.name, value)}
|
||||
defaultValue={currentValue || field.defaultValue || ''}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'select':
|
||||
return (
|
||||
<Select
|
||||
key={field.name}
|
||||
placeholder={field.placeholder}
|
||||
onChange={(e) => handleChange(field.name, e.target.value)}
|
||||
defaultValue={currentValue || field.defaultValue || ''}
|
||||
items={field.options}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'input':
|
||||
return (
|
||||
<Input
|
||||
key={field.name}
|
||||
placeholder={field.placeholder}
|
||||
variant="search"
|
||||
className="w-full md:max-w-[200px]"
|
||||
value={currentValue || field.defaultValue || ''}
|
||||
onChange={(e) => handleInputChange(field.name, e)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// جداسازی فیلد جستجو از سایر فیلدها
|
||||
const searchFieldObj = fields.find(field => field.name === searchField);
|
||||
const otherFields = fields.filter(field => field.name !== searchField);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{/* آیکون فیلتر برای موبایل */}
|
||||
<div className="flex md:hidden items-center justify-between w-full mb-2">
|
||||
<button
|
||||
onClick={() => setIsFiltersOpen(!isFiltersOpen)}
|
||||
className="flex items-center gap-2 px-3 py-2 bg-white border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
||||
>
|
||||
<Filter size={18} color="#000" />
|
||||
<span className="text-sm">فیلترها</span>
|
||||
</button>
|
||||
{searchFieldObj && (
|
||||
<div className="flex-1 mr-4">
|
||||
{renderField(searchFieldObj)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* فیلترها برای دسکتاپ (همیشه نمایش داده میشوند) */}
|
||||
<div className="hidden md:flex md:flex-row justify-between items-center gap-4 w-full">
|
||||
<div className={fieldClassName}>
|
||||
{otherFields.map(renderField)}
|
||||
</div>
|
||||
{searchFieldObj && (
|
||||
<div className="w-full md:w-auto">
|
||||
{renderField(searchFieldObj)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* فیلترها برای موبایل (قابل باز و بسته شدن) */}
|
||||
{isFiltersOpen && (
|
||||
<div className="md:hidden w-full animate-fadeInDown">
|
||||
<div className="bg-gradient-to-br from-white to-gray-50 border border-gray-200 rounded-xl p-5 space-y-4">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-gray-100">
|
||||
<div className="flex items-center gap-2">
|
||||
<Filter size={16} color="#6B7280" />
|
||||
<span className="text-sm font-medium text-gray-700">فیلتر کردن نتایج</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsFiltersOpen(false)}
|
||||
className="p-1 hover:bg-gray-100 rounded-full transition-colors"
|
||||
>
|
||||
<CloseCircle size={18} color="#6B7280" />
|
||||
</button>
|
||||
</div>
|
||||
{otherFields.map(field => (
|
||||
<div key={field.name} className="w-full">
|
||||
<label className="block text-xs font-medium text-gray-600 mb-2">
|
||||
{field.placeholder}
|
||||
</label>
|
||||
<div className="transform hover:scale-[1.02] transition-transform duration-200">
|
||||
{renderField(field)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Filters;
|
||||
Reference in New Issue
Block a user