fix bugs
This commit is contained in:
parent
89c2abd5cf
commit
481e7e748a
|
|
@ -19,13 +19,14 @@ export const FormActions: React.FC<FormActionsProps> = ({
|
|||
className = '',
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex justify-end space-x-4 space-x-reverse pt-6 border-t border-gray-200 dark:border-gray-600 ${className}`}>
|
||||
<div className={`flex flex-col-reverse sm:flex-row justify-end gap-3 sm:space-x-4 sm:space-x-reverse pt-6 border-t border-gray-200 dark:border-gray-600 ${className}`}>
|
||||
{onCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onCancel}
|
||||
disabled={isLoading}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{cancelLabel}
|
||||
</Button>
|
||||
|
|
@ -34,6 +35,7 @@ export const FormActions: React.FC<FormActionsProps> = ({
|
|||
type="submit"
|
||||
loading={isLoading}
|
||||
disabled={isDisabled || isLoading}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
{submitLabel}
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ export const FileUploader: React.FC<FileUploaderProps> = ({
|
|||
{showUploadArea && (
|
||||
<div
|
||||
className={`
|
||||
relative border-2 border-dashed rounded-lg p-6 transition-colors cursor-pointer
|
||||
relative border-2 border-dashed rounded-lg p-4 sm:p-6 min-w-0 transition-colors cursor-pointer
|
||||
${isDragOver ? 'border-primary-400 bg-primary-50 dark:bg-primary-900/20' : 'border-gray-300 dark:border-gray-600'}
|
||||
${disabled ? 'opacity-50 cursor-not-allowed' : 'hover:border-primary-400 hover:bg-gray-50 dark:hover:bg-gray-700'}
|
||||
${error ? 'border-red-300 bg-red-50 dark:bg-red-900/20' : ''}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,16 @@ const toIsoLike = (date?: DateObject | null): string | undefined => {
|
|||
if (!date) return undefined;
|
||||
try {
|
||||
const g = date.convert(undefined);
|
||||
const yyyy = g.year.toString().padStart(4, '0');
|
||||
const mm = g.month.toString().padStart(2, '0');
|
||||
const dd = g.day.toString().padStart(2, '0');
|
||||
const hh = g.hour.toString().padStart(2, '0');
|
||||
const mi = g.minute.toString().padStart(2, '0');
|
||||
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:00Z`;
|
||||
const localDate = new Date(
|
||||
g.year,
|
||||
g.month.number - 1,
|
||||
g.day,
|
||||
g.hour ?? 0,
|
||||
g.minute ?? 0,
|
||||
0,
|
||||
0
|
||||
);
|
||||
return localDate.toISOString();
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -60,7 +64,7 @@ export const JalaliDateTimePicker: React.FC<JalaliDateTimePickerProps> = ({ labe
|
|||
containerClassName="w-full"
|
||||
placeholder={placeholder || 'تاریخ و ساعت'}
|
||||
editable={false}
|
||||
plugins={[<TimePicker key="time" position="bottom" />]}
|
||||
plugins={[<TimePicker key="time" position="bottom" hStep={1} mStep={1} />]}
|
||||
disableMonthPicker={false}
|
||||
disableYearPicker={false}
|
||||
showOtherDays
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ const VariantForm: React.FC<VariantFormProps> = ({ variant, onSave, onCancel, is
|
|||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6 bg-gray-50 dark:bg-gray-700 p-6 rounded-lg border">
|
||||
<div className="space-y-6 bg-gray-50 dark:bg-gray-700 p-4 sm:p-6 rounded-lg border overflow-hidden">
|
||||
<div>
|
||||
<h4 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
{isEdit ? 'ویرایش Variant' : 'افزودن Variant جدید'}
|
||||
|
|
@ -499,26 +499,28 @@ const VariantForm: React.FC<VariantFormProps> = ({ variant, onSave, onCancel, is
|
|||
Meta Data
|
||||
</h5>
|
||||
|
||||
<div className="flex gap-3 mb-3">
|
||||
<input
|
||||
type="text"
|
||||
value={newMetaKey}
|
||||
onChange={(e) => setNewMetaKey(e.target.value)}
|
||||
placeholder="کلید Meta"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={newMetaValue}
|
||||
onChange={(e) => setNewMetaValue(e.target.value)}
|
||||
placeholder="مقدار Meta"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100"
|
||||
/>
|
||||
<div className="flex flex-col sm:flex-row gap-3 mb-3">
|
||||
<div className="flex flex-col sm:flex-row gap-3 flex-1 min-w-0">
|
||||
<input
|
||||
type="text"
|
||||
value={newMetaKey}
|
||||
onChange={(e) => setNewMetaKey(e.target.value)}
|
||||
placeholder="کلید Meta"
|
||||
className="flex-1 min-w-0 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={newMetaValue}
|
||||
onChange={(e) => setNewMetaValue(e.target.value)}
|
||||
placeholder="مقدار Meta"
|
||||
className="flex-1 min-w-0 w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-700 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={handleAddMeta}
|
||||
className="flex items-center gap-2"
|
||||
className="flex items-center justify-center gap-2 w-full sm:w-auto shrink-0"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
افزودن
|
||||
|
|
@ -528,14 +530,14 @@ const VariantForm: React.FC<VariantFormProps> = ({ variant, onSave, onCancel, is
|
|||
{Object.keys(meta).length > 0 && (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-2">
|
||||
{Object.entries(meta).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center justify-between bg-white dark:bg-gray-600 px-3 py-2 rounded-md border">
|
||||
<span className="text-sm">
|
||||
<div key={key} className="flex items-center justify-between gap-2 bg-white dark:bg-gray-600 px-3 py-2 rounded-md border min-w-0">
|
||||
<span className="text-sm truncate min-w-0">
|
||||
<strong>{key}:</strong> {String(value)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleRemoveMeta(key)}
|
||||
className="text-red-500 hover:text-red-700"
|
||||
className="text-red-500 hover:text-red-700 shrink-0"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
|
|
@ -559,11 +561,11 @@ const VariantForm: React.FC<VariantFormProps> = ({ variant, onSave, onCancel, is
|
|||
</div>
|
||||
|
||||
{/* Action Buttons */}
|
||||
<div className="flex justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<Button variant="secondary" onClick={onCancel}>
|
||||
<div className="flex flex-col-reverse sm:flex-row justify-end gap-3 pt-4 border-t border-gray-200 dark:border-gray-600">
|
||||
<Button variant="secondary" onClick={onCancel} className="w-full sm:w-auto">
|
||||
انصراف
|
||||
</Button>
|
||||
<Button onClick={handleSave}>
|
||||
<Button onClick={handleSave} className="w-full sm:w-auto">
|
||||
{isEdit ? 'بهروزرسانی' : 'افزودن'}
|
||||
</Button>
|
||||
</div>
|
||||
|
|
@ -611,12 +613,12 @@ export const VariantManager: React.FC<VariantManagerProps> = ({ variants, onChan
|
|||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<h3 className="text-lg font-medium text-gray-900 dark:text-gray-100">
|
||||
Variants محصول ({variants.length})
|
||||
</h3>
|
||||
{!disabled && !showForm && (
|
||||
<Button onClick={handleAddVariant} className="flex items-center gap-2">
|
||||
<Button onClick={handleAddVariant} className="flex items-center justify-center gap-2 w-full sm:w-auto shrink-0">
|
||||
<Plus className="h-4 w-4" />
|
||||
افزودن Variant
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ const ProductDetailPage = () => {
|
|||
{product.sku && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
کد محصول (SKU)
|
||||
کد محصول
|
||||
</label>
|
||||
<div className="p-3 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p className="text-gray-900 dark:text-gray-100 font-mono">
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ const ProductFormPage = () => {
|
|||
/>
|
||||
|
||||
<Input
|
||||
label="SKU"
|
||||
label="کد محصول"
|
||||
{...register('sku')}
|
||||
error={errors.sku?.message}
|
||||
placeholder="مثال: RING-001"
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ const ProductsListPage = () => {
|
|||
</div>
|
||||
{product.sku && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
SKU: {product.sku}
|
||||
کد محصول: {product.sku}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useMemo, useEffect } from 'react';
|
||||
import { usePaymentMethodsReport } from '../core/_hooks';
|
||||
import { PaymentMethodsFilters } from '../core/_models';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
|
@ -13,6 +13,8 @@ import { formatWithThousands, persianToEnglish } from '@/utils/numberUtils';
|
|||
import { PieChart } from '@/components/charts/PieChart';
|
||||
import { formatCurrency, formatDateTime } from '@/utils/formatters';
|
||||
import { ReportSkeleton } from '@/components/common/ReportSkeleton';
|
||||
import { useSearchUsers, useUsers } from '@/pages/users-admin/core/_hooks';
|
||||
import { UserFilters } from '@/pages/users-admin/core/_models';
|
||||
|
||||
const formatPercentage = (value: number) => {
|
||||
return formatWithThousands(value.toFixed(2)) + '%';
|
||||
|
|
@ -42,8 +44,31 @@ const PaymentMethodsReportPage = () => {
|
|||
group_by_user: false,
|
||||
});
|
||||
|
||||
const [userSearchText, setUserSearchText] = useState('');
|
||||
const [userSearchDebounced, setUserSearchDebounced] = useState('');
|
||||
const [userDropdownOpen, setUserDropdownOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const t = setTimeout(() => setUserSearchDebounced(userSearchText), 300);
|
||||
return () => clearTimeout(t);
|
||||
}, [userSearchText]);
|
||||
|
||||
const { data: usersList } = useUsers({ limit: 20, offset: 0 });
|
||||
const userSearchFilters = useMemo<UserFilters>(
|
||||
() => (userSearchDebounced ? { search_text: userSearchDebounced, limit: 20, offset: 0 } : {}),
|
||||
[userSearchDebounced]
|
||||
);
|
||||
const { data: userSearchData } = useSearchUsers(userSearchFilters);
|
||||
const userOptions = userSearchDebounced ? (userSearchData?.users ?? []) : (usersList ?? []);
|
||||
|
||||
const { data, isLoading, error } = usePaymentMethodsReport(filters);
|
||||
|
||||
const handleSelectUser = (user: { id: number; phone_number?: string; first_name?: string; last_name?: string }) => {
|
||||
handleTempFilterChange('user_id', user.id);
|
||||
setUserSearchText(user.phone_number || `${user.first_name || ''} ${user.last_name || ''}`.trim() || String(user.id));
|
||||
setUserDropdownOpen(false);
|
||||
};
|
||||
|
||||
const handleTempFilterChange = (key: keyof PaymentMethodsFilters, value: any) => {
|
||||
setTempFilters(prev => ({
|
||||
...prev,
|
||||
|
|
@ -61,12 +86,6 @@ const PaymentMethodsReportPage = () => {
|
|||
}));
|
||||
};
|
||||
|
||||
const handleNumericFilterChange = (key: 'user_id', raw: string) => {
|
||||
const converted = persianToEnglish(raw).replace(/[^\d]/g, '');
|
||||
const numeric = converted ? Number(converted) : undefined;
|
||||
handleTempFilterChange(key, numeric);
|
||||
};
|
||||
|
||||
const handleApplyFilters = () => {
|
||||
setFilters({
|
||||
...tempFilters,
|
||||
|
|
@ -82,13 +101,14 @@ const PaymentMethodsReportPage = () => {
|
|||
};
|
||||
|
||||
const handleClearFilters = () => {
|
||||
const clearedFilters = {
|
||||
const clearedFilters: PaymentMethodsFilters = {
|
||||
limit: 50,
|
||||
offset: 0,
|
||||
group_by_user: false,
|
||||
};
|
||||
setTempFilters(clearedFilters);
|
||||
setFilters(clearedFilters);
|
||||
setUserSearchText('');
|
||||
};
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
|
|
@ -191,17 +211,55 @@ const PaymentMethodsReportPage = () => {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-4 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg">
|
||||
<p className="text-sm text-blue-800 dark:text-blue-200">
|
||||
<span className="font-semibold">توجه:</span> برای فیلتر بر اساس کاربر، کاربر را از لیست انتخاب کنید یا با شماره موبایل جستجو کنید.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
شناسه کاربر
|
||||
کاربر
|
||||
</label>
|
||||
<Input
|
||||
value={tempFilters.user_id?.toString() || ''}
|
||||
onChange={(e) => handleNumericFilterChange('user_id', e.target.value)}
|
||||
placeholder="مثلاً 456"
|
||||
numeric
|
||||
/>
|
||||
<div className="relative">
|
||||
<Input
|
||||
value={userSearchText}
|
||||
onChange={(e) => {
|
||||
setUserSearchText(persianToEnglish(e.target.value));
|
||||
setUserDropdownOpen(true);
|
||||
if (tempFilters.user_id) handleTempFilterChange('user_id', undefined);
|
||||
}}
|
||||
onFocus={() => setUserDropdownOpen(true)}
|
||||
onBlur={() => setTimeout(() => setUserDropdownOpen(false), 150)}
|
||||
placeholder="جستجو با شماره موبایل یا انتخاب از لیست"
|
||||
/>
|
||||
{userDropdownOpen && (
|
||||
<div className="absolute z-20 mt-2 w-full rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 shadow-lg max-h-60 overflow-y-auto">
|
||||
{userOptions.length === 0 && (
|
||||
<div className="p-3 text-sm text-gray-500 dark:text-gray-400">
|
||||
کاربری یافت نشد
|
||||
</div>
|
||||
)}
|
||||
{userOptions.map((user: { id: number; phone_number?: string; first_name?: string; last_name?: string }) => (
|
||||
<button
|
||||
key={user.id}
|
||||
type="button"
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
onClick={() => handleSelectUser(user)}
|
||||
className="w-full text-right px-4 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
<div className="text-sm text-gray-900 dark:text-gray-100">
|
||||
{user.first_name} {user.last_name}
|
||||
</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{user.phone_number}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const ProfitLossReportPage = () => {
|
|||
<div>
|
||||
<div className="font-medium">{row.product_name}</div>
|
||||
{row.product_sku && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">SKU: {row.product_sku}</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">کد محصول: {row.product_sku}</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
|
|
@ -226,7 +226,7 @@ const ProfitLossReportPage = () => {
|
|||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
SKU محصول
|
||||
کد محصول
|
||||
</label>
|
||||
<Input
|
||||
value={tempFilters.product_sku || ''}
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ const SalesSummaryReportPage = () => {
|
|||
<div>
|
||||
<div className="font-medium">{row.product_name}</div>
|
||||
{row.product_sku && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">SKU: {row.product_sku}</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">کد محصول: {row.product_sku}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -260,7 +260,7 @@ const SalesSummaryReportPage = () => {
|
|||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
SKU محصول
|
||||
کد محصول
|
||||
</label>
|
||||
<Input
|
||||
value={tempFilters.product_sku || ''}
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ const VariantComparisonReportPage = () => {
|
|||
<div>
|
||||
<div className="font-medium">{row.product_name}</div>
|
||||
{row.product_sku && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">SKU: {row.product_sku}</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">کد محصول: {row.product_sku}</div>
|
||||
)}
|
||||
{(row.variant_size || row.variant_color) && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
|
|
@ -198,7 +198,7 @@ const VariantComparisonReportPage = () => {
|
|||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
SKU محصول
|
||||
کد محصول
|
||||
</label>
|
||||
<Input
|
||||
value={tempFilters.product_sku || ''}
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ export const useVerifyUser = () => {
|
|||
mutationFn: (id: string) => verifyUser(id),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.GET_USERS] });
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.SEARCH_USERS] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QUERY_KEY.GET_USER, variables],
|
||||
});
|
||||
|
|
@ -212,6 +213,7 @@ export const useUnverifyUser = () => {
|
|||
mutationFn: (id: string) => unverifyUser(id),
|
||||
onSuccess: (data, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.GET_USERS] });
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY.SEARCH_USERS] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [QUERY_KEY.GET_USER, variables],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -193,14 +193,14 @@ const UsersAdminListPage: React.FC = () => {
|
|||
</button>
|
||||
<button
|
||||
onClick={() => handleVerifyToggle(row)}
|
||||
className={`${row.verified
|
||||
className={`${!row.verified
|
||||
? 'text-yellow-600 hover:text-yellow-900 dark:text-yellow-400 dark:hover:text-yellow-300'
|
||||
: 'text-green-600 hover:text-green-900 dark:text-green-400 dark:hover:text-green-300'
|
||||
}`}
|
||||
title={row.verified ? 'لغو تأیید' : 'تأیید کاربر'}
|
||||
data-testid={`verify-user-${row.id}`}
|
||||
>
|
||||
{row.verified ? <UserX className="h-4 w-4" /> : <UserCheck className="h-4 w-4" />}
|
||||
{!row.verified ? <UserX className="h-4 w-4" /> : <UserCheck className="h-4 w-4" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteClick(row)}
|
||||
|
|
@ -265,42 +265,42 @@ const UsersAdminListPage: React.FC = () => {
|
|||
)}
|
||||
|
||||
<FiltersSection isLoading={false} columns={3}>
|
||||
<Input
|
||||
placeholder="جستجو بر اساس نام، شماره تلفن یا ایمیل..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
||||
data-testid="search-users-input"
|
||||
/>
|
||||
<select
|
||||
value={selectedStatus}
|
||||
onChange={(e) => setSelectedStatus(e.target.value as any)}
|
||||
className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200"
|
||||
data-testid="status-filter-select"
|
||||
<Input
|
||||
placeholder="جستجو بر اساس نام، شماره تلفن یا ایمیل..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
||||
data-testid="search-users-input"
|
||||
/>
|
||||
<select
|
||||
value={selectedStatus}
|
||||
onChange={(e) => setSelectedStatus(e.target.value as any)}
|
||||
className="w-full px-3 py-3 text-base border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:text-gray-100 transition-all duration-200"
|
||||
data-testid="status-filter-select"
|
||||
>
|
||||
<option value="all">همه کاربران</option>
|
||||
<option value="verified">تأیید شده</option>
|
||||
<option value="unverified">تأیید نشده</option>
|
||||
</select>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleSearch}
|
||||
className="flex items-center gap-2"
|
||||
data-testid="search-button"
|
||||
>
|
||||
<option value="all">همه کاربران</option>
|
||||
<option value="verified">تأیید شده</option>
|
||||
<option value="unverified">تأیید نشده</option>
|
||||
</select>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={handleSearch}
|
||||
className="flex items-center gap-2"
|
||||
data-testid="search-button"
|
||||
>
|
||||
<Search className="h-4 w-4" />
|
||||
جستجو
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleClearFilters}
|
||||
className="flex items-center gap-2"
|
||||
data-testid="clear-filters-button"
|
||||
>
|
||||
<Filter className="h-4 w-4" />
|
||||
پاک کردن فیلترها
|
||||
</Button>
|
||||
</div>
|
||||
<Search className="h-4 w-4" />
|
||||
جستجو
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleClearFilters}
|
||||
className="flex items-center gap-2"
|
||||
data-testid="clear-filters-button"
|
||||
>
|
||||
<Filter className="h-4 w-4" />
|
||||
پاک کردن فیلترها
|
||||
</Button>
|
||||
</div>
|
||||
</FiltersSection>
|
||||
|
||||
{/* Users Table */}
|
||||
|
|
|
|||
Loading…
Reference in New Issue