feat(discount-codes): update DiscountCodeFormPage with date picker and single user selection
This commit is contained in:
parent
f69e48dc0e
commit
fbffc716ba
|
|
@ -18,9 +18,11 @@
|
|||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.263.1",
|
||||
"react": "^18.2.0",
|
||||
"react-date-object": "2.1.9",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-multi-date-picker": "4.5.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"recharts": "^2.8.0",
|
||||
"yup": "^1.6.1",
|
||||
|
|
@ -5796,6 +5798,12 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-date-object": {
|
||||
"version": "2.1.9",
|
||||
"resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz",
|
||||
"integrity": "sha512-BHxD/quWOTo9fLKV/cfL/M31ePoj4a1JaJ/CnOf8Ndg3mrkh4x9wEMMkCfTrzduxDOgU8ZgR8uarhqI5G71sTg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
|
|
@ -5809,6 +5817,16 @@
|
|||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-element-popper": {
|
||||
"version": "2.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.7.tgz",
|
||||
"integrity": "sha512-tuM2OxKlW32h+6uFSK6EENHPeZ2OGgOipHfOAl+VLWEv9/j3QkSGbD+ADX3A9uJlmq24i37n28RjJmAbGTfpEg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hook-form": {
|
||||
"version": "7.57.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.57.0.tgz",
|
||||
|
|
@ -5848,6 +5866,20 @@
|
|||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-multi-date-picker": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
||||
"integrity": "sha512-FgWjZB3Z6IA6XpcWiLPk85PwcRUhOiYhKK42o5k672gD/n2I6rzPfQ8bUrldOIiF/Z7FfOCdH7a6FeubzqteLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-date-object": "^2.1.8",
|
||||
"react-element-popper": "^2.1.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
|
|
|
|||
|
|
@ -26,9 +26,11 @@
|
|||
"js-cookie": "^3.0.5",
|
||||
"lucide-react": "^0.263.1",
|
||||
"react": "^18.2.0",
|
||||
"react-date-object": "2.1.9",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.57.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-multi-date-picker": "4.5.2",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"recharts": "^2.8.0",
|
||||
"yup": "^1.6.1",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,75 @@
|
|||
import React from 'react';
|
||||
import DatePicker from 'react-multi-date-picker';
|
||||
import TimePicker from 'react-multi-date-picker/plugins/time_picker';
|
||||
import persian from 'react-date-object/calendars/persian';
|
||||
import persian_fa from 'react-date-object/locales/persian_fa';
|
||||
import DateObject from 'react-date-object';
|
||||
import { Label } from './Typography';
|
||||
|
||||
interface JalaliDateTimePickerProps {
|
||||
label?: string;
|
||||
value?: string | null;
|
||||
onChange: (value: string | undefined) => void;
|
||||
error?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const toIsoLike = (date?: DateObject | null): string | undefined => {
|
||||
if (!date) return undefined;
|
||||
try {
|
||||
const g = date.convert();
|
||||
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`;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const fromIsoToDateObject = (value?: string | null): DateObject | undefined => {
|
||||
if (!value) return undefined;
|
||||
try {
|
||||
const d = new Date(value);
|
||||
if (isNaN(d.getTime())) return undefined;
|
||||
return new DateObject(d).convert(persian, persian_fa);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const JalaliDateTimePicker: React.FC<JalaliDateTimePickerProps> = ({ label, value, onChange, error, placeholder }) => {
|
||||
const selected = fromIsoToDateObject(value);
|
||||
|
||||
return (
|
||||
<div className="space-y-1">
|
||||
{label && <Label>{label}</Label>}
|
||||
<DatePicker
|
||||
value={selected}
|
||||
onChange={(val) => onChange(toIsoLike(val as DateObject | null))}
|
||||
format="YYYY/MM/DD HH:mm"
|
||||
calendar={persian}
|
||||
locale={persian_fa}
|
||||
calendarPosition="bottom-center"
|
||||
disableDayPicker={false}
|
||||
inputClass={`w-full border rounded-lg px-3 py-3 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:ring-2 ${error ? 'border-red-300 focus:border-red-500 focus:ring-red-500' : 'border-gray-300 dark:border-gray-600 focus:border-primary-500 focus:ring-primary-500'}`}
|
||||
containerClassName="w-full"
|
||||
placeholder={placeholder || 'تاریخ و ساعت'}
|
||||
editable={false}
|
||||
plugins={[<TimePicker key="time" position="bottom" />]}
|
||||
disableMonthPicker={false}
|
||||
disableYearPicker={false}
|
||||
showOtherDays
|
||||
/>
|
||||
{error && (
|
||||
<p className="text-xs text-red-600 dark:text-red-400" role="alert">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JalaliDateTimePicker;
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { ChevronDown, X } from 'lucide-react';
|
||||
|
||||
export interface Option {
|
||||
id: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface SingleSelectAutocompleteProps {
|
||||
options: Option[];
|
||||
selectedValue?: number;
|
||||
onChange: (value?: number) => void;
|
||||
placeholder?: string;
|
||||
label?: string;
|
||||
error?: string;
|
||||
isLoading?: boolean;
|
||||
disabled?: boolean;
|
||||
onSearchChange?: (query: string) => void;
|
||||
onLoadMore?: () => void;
|
||||
hasMore?: boolean;
|
||||
loadingMore?: boolean;
|
||||
}
|
||||
|
||||
export const SingleSelectAutocomplete: React.FC<SingleSelectAutocompleteProps> = ({
|
||||
options,
|
||||
selectedValue,
|
||||
onChange,
|
||||
placeholder = "انتخاب کنید...",
|
||||
label,
|
||||
error,
|
||||
isLoading = false,
|
||||
disabled = false,
|
||||
onSearchChange,
|
||||
onLoadMore,
|
||||
hasMore = false,
|
||||
loadingMore = false,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const listRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const filteredOptions = options.filter(option =>
|
||||
option.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(option.description && option.description.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
);
|
||||
|
||||
const displayedOptions = onSearchChange ? options : filteredOptions;
|
||||
const selectedOption = options.find(option => option.id === selectedValue);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||
setIsOpen(false);
|
||||
setSearchTerm('');
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const handleSelectOption = (optionId: number) => {
|
||||
onChange(optionId);
|
||||
setIsOpen(false);
|
||||
setSearchTerm('');
|
||||
};
|
||||
|
||||
const handleClearSelection = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onChange(undefined);
|
||||
};
|
||||
|
||||
const handleToggleDropdown = () => {
|
||||
if (disabled) return;
|
||||
setIsOpen(!isOpen);
|
||||
if (!isOpen) {
|
||||
setTimeout(() => inputRef.current?.focus(), 100);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
setSearchTerm(value);
|
||||
if (onSearchChange) {
|
||||
onSearchChange(value);
|
||||
}
|
||||
};
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
|
||||
if (scrollHeight - scrollTop <= clientHeight + 5 && hasMore && !loadingMore && onLoadMore) {
|
||||
onLoadMore();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
{label && (
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`
|
||||
w-full min-h-[42px] px-3 py-2 border rounded-md
|
||||
focus-within:outline-none focus-within:ring-1 focus-within:ring-primary-500
|
||||
cursor-pointer
|
||||
${error ? 'border-red-500' : 'border-gray-300 dark:border-gray-600'}
|
||||
${disabled ? 'bg-gray-100 cursor-not-allowed' : 'bg-white dark:bg-gray-700'}
|
||||
dark:text-gray-100
|
||||
`}
|
||||
onClick={handleToggleDropdown}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
{selectedOption ? (
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className="text-sm font-medium">{selectedOption.title}</span>
|
||||
{selectedOption.description && (
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400 block">
|
||||
{selectedOption.description}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleClearSelection}
|
||||
className="ml-2 p-1 hover:bg-gray-200 dark:hover:bg-gray-600 rounded"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-gray-500 dark:text-gray-400">{placeholder}</span>
|
||||
)}
|
||||
</div>
|
||||
<ChevronDown className={`h-4 w-4 transition-transform ${isOpen ? 'rotate-180' : ''}`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="absolute z-50 w-full mt-1 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md shadow-lg">
|
||||
<div className="p-2 border-b border-gray-200 dark:border-gray-600">
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={searchTerm}
|
||||
onChange={handleSearchChange}
|
||||
placeholder="جستجو..."
|
||||
className="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-1 focus:ring-primary-500 dark:bg-gray-600 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={listRef}
|
||||
className="max-h-60 overflow-y-auto"
|
||||
onScroll={handleScroll}
|
||||
>
|
||||
{isLoading && displayedOptions.length === 0 ? (
|
||||
<div className="p-3 text-center text-gray-500 dark:text-gray-400">
|
||||
در حال بارگذاری...
|
||||
</div>
|
||||
) : displayedOptions.length === 0 ? (
|
||||
<div className="p-3 text-center text-gray-500 dark:text-gray-400">
|
||||
موردی یافت نشد
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{displayedOptions.map((option) => (
|
||||
<div
|
||||
key={option.id}
|
||||
className={`
|
||||
p-3 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-600
|
||||
${selectedValue === option.id ? 'bg-blue-50 dark:bg-blue-900' : ''}
|
||||
`}
|
||||
onClick={() => handleSelectOption(option.id)}
|
||||
>
|
||||
<div className="font-medium text-sm">{option.title}</div>
|
||||
{option.description && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
{option.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{loadingMore && (
|
||||
<div className="p-3 text-center text-gray-500 dark:text-gray-400">
|
||||
در حال بارگذاری بیشتر...
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<p className="mt-1 text-sm text-red-600 dark:text-red-400">{error}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
import { parseFormattedNumber } from '@/utils/numberUtils';
|
||||
|
|
@ -10,6 +10,8 @@ import { Button } from "@/components/ui/Button";
|
|||
import { Input } from "@/components/ui/Input";
|
||||
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
|
||||
import { MultiSelectAutocomplete, Option } from "@/components/ui/MultiSelectAutocomplete";
|
||||
import { SingleSelectAutocomplete } from "@/components/ui/SingleSelectAutocomplete";
|
||||
import { JalaliDateTimePicker } from "@/components/ui/JalaliDateTimePicker";
|
||||
import { FormHeader, PageContainer, Label, SectionTitle } from '../../../components/ui/Typography';
|
||||
import { ArrowRight, BadgePercent, Calendar, Settings, Users, Tag, Info } from 'lucide-react';
|
||||
import { useUsers, useSearchUsers } from '../../users-admin/core/_hooks';
|
||||
|
|
@ -29,7 +31,7 @@ const schema = yup.object({
|
|||
.min(0.01, 'مقدار باید بیشتر از صفر باشد')
|
||||
.max(999999, 'مقدار نباید بیشتر از ۹۹۹,۹۹۹ باشد'),
|
||||
status: yup.mixed<'active' | 'inactive'>().oneOf(['active', 'inactive']).required('وضعیت الزامی است'),
|
||||
application_level: yup.array().of(yup.mixed<'invoice' | 'category' | 'product' | 'shipping' | 'product_fee'>().oneOf(['invoice', 'category', 'product', 'shipping', 'product_fee'])).min(1, 'حداقل یک سطح اعمال انتخاب کنید').required('سطح اعمال الزامی است'),
|
||||
application_level: yup.mixed<'invoice' | 'category' | 'product' | 'shipping' | 'product_fee'>().oneOf(['invoice', 'category', 'product', 'shipping', 'product_fee']).required('سطح اعمال الزامی است'),
|
||||
min_purchase_amount: yup
|
||||
.number()
|
||||
.transform((val, original) => parseFormattedNumber(original) as any)
|
||||
|
|
@ -77,10 +79,10 @@ const DiscountCodeFormPage = () => {
|
|||
const { id } = useParams();
|
||||
const isEdit = !!id;
|
||||
|
||||
const [selectedUserIds, setSelectedUserIds] = useState<number[]>([]);
|
||||
const [selectedProductIds, setSelectedProductIds] = useState<number[]>([]);
|
||||
const [selectedCategoryIds, setSelectedCategoryIds] = useState<number[]>([]);
|
||||
const [selectedApplicationLevels, setSelectedApplicationLevels] = useState<string[]>(['invoice']);
|
||||
const [selectedApplicationLevel, setSelectedApplicationLevel] = useState<string>('invoice');
|
||||
const [selectedUserId, setSelectedUserId] = useState<number | undefined>();
|
||||
|
||||
const { data: dc, isLoading: dcLoading } = useDiscountCode(id || '');
|
||||
const { mutate: create, isPending: creating } = useCreateDiscountCode();
|
||||
|
|
@ -177,10 +179,10 @@ const DiscountCodeFormPage = () => {
|
|||
description: category.description || `دستهبندی #${category.id}`
|
||||
}));
|
||||
|
||||
const { register, handleSubmit, formState: { errors, isValid }, reset, watch } = useForm<CreateDiscountCodeRequest>({
|
||||
const { register, handleSubmit, control, formState: { errors, isValid }, reset, watch } = useForm<CreateDiscountCodeRequest>({
|
||||
resolver: yupResolver(schema as any),
|
||||
mode: 'onChange',
|
||||
defaultValues: { status: 'active', type: 'percentage', application_level: ['invoice'], single_use: false }
|
||||
defaultValues: { status: 'active', type: 'percentage', application_level: 'invoice', single_use: false }
|
||||
});
|
||||
|
||||
const applicationLevel = watch('application_level');
|
||||
|
|
@ -206,17 +208,17 @@ const DiscountCodeFormPage = () => {
|
|||
meta: dc.meta,
|
||||
});
|
||||
|
||||
// Set selected user IDs
|
||||
if (dc.user_restrictions?.user_ids) {
|
||||
setSelectedUserIds(dc.user_restrictions.user_ids);
|
||||
// Set selected user ID (first one if multiple)
|
||||
if (dc.user_restrictions?.user_ids && dc.user_restrictions.user_ids.length > 0) {
|
||||
setSelectedUserId(dc.user_restrictions.user_ids[0]);
|
||||
}
|
||||
|
||||
// Set selected application levels
|
||||
// Set selected application level
|
||||
if (dc.application_level) {
|
||||
setSelectedApplicationLevels(
|
||||
setSelectedApplicationLevel(
|
||||
Array.isArray(dc.application_level)
|
||||
? dc.application_level
|
||||
: [dc.application_level]
|
||||
? dc.application_level[0]
|
||||
: dc.application_level
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -238,14 +240,12 @@ const DiscountCodeFormPage = () => {
|
|||
|
||||
const formData: CreateDiscountCodeRequest = {
|
||||
...data,
|
||||
application_level: selectedApplicationLevels.length === 1
|
||||
? selectedApplicationLevels[0] as any
|
||||
: selectedApplicationLevels as any,
|
||||
application_level: selectedApplicationLevel as any,
|
||||
valid_from: toApiDateTime(data.valid_from),
|
||||
valid_to: toApiDateTime(data.valid_to),
|
||||
user_restrictions: selectedUserIds.length > 0 ? {
|
||||
user_restrictions: selectedUserId ? {
|
||||
...cleanRestrictions,
|
||||
user_ids: selectedUserIds,
|
||||
user_ids: [selectedUserId],
|
||||
} : cleanRestrictions.user_group ? cleanRestrictions : undefined,
|
||||
product_ids: selectedProductIds.length > 0 ? selectedProductIds : undefined,
|
||||
category_ids: selectedCategoryIds.length > 0 ? selectedCategoryIds : undefined,
|
||||
|
|
@ -414,7 +414,7 @@ const DiscountCodeFormPage = () => {
|
|||
color: 'red'
|
||||
}
|
||||
].map(option => {
|
||||
const isSelected = selectedApplicationLevels.includes(option.value);
|
||||
const isSelected = selectedApplicationLevel === option.value;
|
||||
|
||||
const getColorClasses = (color: string, selected: boolean) => {
|
||||
const baseClasses = 'relative flex flex-col p-4 border-2 rounded-xl cursor-pointer transition-all duration-200 hover:shadow-lg transform hover:-translate-y-1';
|
||||
|
|
@ -468,15 +468,11 @@ const DiscountCodeFormPage = () => {
|
|||
className={getColorClasses(option.color, isSelected)}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
type="radio"
|
||||
name="application_level"
|
||||
value={option.value}
|
||||
checked={isSelected}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedApplicationLevels(prev => [...prev, option.value]);
|
||||
} else {
|
||||
setSelectedApplicationLevels(prev => prev.filter(level => level !== option.value));
|
||||
}
|
||||
}}
|
||||
onChange={() => setSelectedApplicationLevel(option.value)}
|
||||
className="sr-only"
|
||||
/>
|
||||
|
||||
|
|
@ -511,7 +507,7 @@ const DiscountCodeFormPage = () => {
|
|||
</div>
|
||||
|
||||
{/* Conditional Product Selection */}
|
||||
{selectedApplicationLevels.includes('product') && (
|
||||
{selectedApplicationLevel === 'product' && (
|
||||
<div className="lg:col-span-3">
|
||||
<div className="bg-gradient-to-r from-purple-50 to-purple-100 dark:from-purple-900/20 dark:to-purple-800/20 rounded-xl p-6 border border-purple-200 dark:border-purple-700">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
|
|
@ -553,7 +549,7 @@ const DiscountCodeFormPage = () => {
|
|||
)}
|
||||
|
||||
{/* Conditional Category Selection */}
|
||||
{selectedApplicationLevels.includes('category') && (
|
||||
{selectedApplicationLevel === 'category' && (
|
||||
<div className="lg:col-span-3">
|
||||
<div className="bg-gradient-to-r from-green-50 to-green-100 dark:from-green-900/20 dark:to-green-800/20 rounded-xl p-6 border border-green-200 dark:border-green-700">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
|
|
@ -656,17 +652,29 @@ const DiscountCodeFormPage = () => {
|
|||
</div>
|
||||
<div className="p-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Input
|
||||
label="شروع اعتبار"
|
||||
type="datetime-local"
|
||||
error={errors.valid_from?.message as string}
|
||||
{...register('valid_from')}
|
||||
<Controller
|
||||
name="valid_from"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<JalaliDateTimePicker
|
||||
label="شروع اعتبار"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={errors.valid_from?.message as string}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Input
|
||||
label="پایان اعتبار"
|
||||
type="datetime-local"
|
||||
error={errors.valid_to?.message as string}
|
||||
{...register('valid_to')}
|
||||
<Controller
|
||||
name="valid_to"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<JalaliDateTimePicker
|
||||
label="پایان اعتبار"
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
error={errors.valid_to?.message as string}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -700,12 +708,12 @@ const DiscountCodeFormPage = () => {
|
|||
|
||||
{/* User Selection */}
|
||||
<div className="border-t border-gray-200 dark:border-gray-700 pt-6">
|
||||
<MultiSelectAutocomplete
|
||||
label="انتخاب کاربران خاص"
|
||||
<SingleSelectAutocomplete
|
||||
label="انتخاب کاربر خاص"
|
||||
options={userOptions}
|
||||
selectedValues={selectedUserIds}
|
||||
onChange={setSelectedUserIds}
|
||||
placeholder="جستجو و انتخاب کاربران..."
|
||||
selectedValue={selectedUserId}
|
||||
onChange={setSelectedUserId}
|
||||
placeholder="جستجو و انتخاب کاربر..."
|
||||
isLoading={usersLoading && userOffset === 0}
|
||||
disabled={false}
|
||||
onSearchChange={(q) => { setUserSearch(q); setUserOffset(0); }}
|
||||
|
|
@ -718,7 +726,7 @@ const DiscountCodeFormPage = () => {
|
|||
loadingMore={usersLoading && userOffset > 0}
|
||||
/>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mt-2">
|
||||
در صورت انتخاب کاربران، کد تخفیف فقط برای آنها قابل استفاده خواهد بود.
|
||||
در صورت انتخاب کاربر، کد تخفیف فقط برای آن کاربر قابل استفاده خواهد بود.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue