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