new UX for new request

This commit is contained in:
2026-05-24 15:50:02 +03:30
parent 131220fcd9
commit 86475dc72a
5 changed files with 400 additions and 123 deletions
+39 -2
View File
@@ -1,7 +1,9 @@
import { type FC, useState, useMemo, useEffect } from 'react' import { type FC, useState, useMemo, useEffect } from 'react'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import { useParams } from 'react-router-dom' import { Link, useParams } from 'react-router-dom'
import { useQueryClient } from '@tanstack/react-query'
import { Paths } from '@/config/Paths'
import Input from '@/components/Input' import Input from '@/components/Input'
import Select from '@/components/Select' import Select from '@/components/Select'
import type { ItemsSelectType } from '@/components/Select' import type { ItemsSelectType } from '@/components/Select'
@@ -39,10 +41,12 @@ const PayInvoice: FC = () => {
const { data: user } = useGetMe() const { data: user } = useGetMe()
const { id } = useParams() const { id } = useParams()
const queryClient = useQueryClient()
const { data, isPending } = useGetInvoiceDetail(id ?? '') const { data, isPending } = useGetInvoiceDetail(id ?? '')
const { mutate: payInvoice, isPending: isPaying } = usePayInvoice() const { mutate: payInvoice, isPending: isPaying } = usePayInvoice()
const multiUpload = useMultiUpload() const multiUpload = useMultiUpload()
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([]) const [attachmentFiles, setAttachmentFiles] = useState<File[]>([])
const [isPaymentDone, setIsPaymentDone] = useState(false)
const userCredit = user?.maxCredit ?? 0 const userCredit = user?.maxCredit ?? 0
const methodItems = useMemo(() => getMethodItems(userCredit), [userCredit]) const methodItems = useMemo(() => getMethodItems(userCredit), [userCredit])
@@ -103,13 +107,25 @@ const PayInvoice: FC = () => {
}, },
}, },
{ {
onSuccess: (res) => { onSuccess: (res, variables) => {
const paymentUrl = res?.data?.paymentUrl const paymentUrl = res?.data?.paymentUrl
if (paymentUrl) { if (paymentUrl) {
toast('در حال انتقال به درگاه پرداخت', 'success') toast('در حال انتقال به درگاه پرداخت', 'success')
window.location.href = paymentUrl window.location.href = paymentUrl
return return
} }
const method = variables.params.method
if (
method === PaymentMethodEnum.Cash ||
method === PaymentMethodEnum.Credit
) {
queryClient.invalidateQueries({ queryKey: ['invoice', id] })
queryClient.invalidateQueries({ queryKey: ['payment', id] })
setIsPaymentDone(true)
return
}
toast('پرداخت با موفقیت ثبت شد', 'success') toast('پرداخت با موفقیت ثبت شد', 'success')
}, },
onError: (error: ErrorType) => { onError: (error: ErrorType) => {
@@ -139,6 +155,27 @@ const PayInvoice: FC = () => {
) )
} }
if (isPaymentDone) {
return (
<div className="mt-4 text-sm">
<div className="bg-white rounded-2xl p-8 flex flex-col items-center justify-center min-h-[280px] gap-6">
<TickCircle size={64} color="#22C55E" variant="Bold" />
<div className="text-center">
<div className="text-lg font-medium text-black mb-2">پرداخت انجام شد</div>
<p className="text-xs text-[#8C90A3]">
پرداخت شما با موفقیت ثبت شد.
</p>
</div>
<Link to={`${Paths.proformaInvoice}/${id}`} className="w-full max-w-xs">
<Button type="button" className="w-full">
بازگشت به جزئیات صورت حساب
</Button>
</Link>
</div>
</div>
)
}
return ( return (
<div className="mt-4 text-sm"> <div className="mt-4 text-sm">
<div className="text-sm mb-6">پرداخت صورت حساب</div> <div className="text-sm mb-6">پرداخت صورت حساب</div>
+91 -40
View File
@@ -1,64 +1,115 @@
import { useState, type FC } from 'react' import { useEffect, useState, type FC } from 'react'
import Request from './components/Request' import Request from './components/Request'
import Button from '@/components/Button' import RequestItemsList from './components/RequestItemsList'
import { TickSquare } from 'iconsax-react'
import { useRequestStore } from './store/RequestStore' import { useRequestStore } from './store/RequestStore'
import { useSubmitRequest } from './hooks/useRequestData' import { useSubmitRequest } from './hooks/useRequestData'
import { toast } from '@/shared/toast' import { toast } from '@/shared/toast'
import { useNavigate } from 'react-router-dom' import { Link, useNavigate } from 'react-router-dom'
import { Paths } from '@/config/Paths' import { Paths } from '@/config/Paths'
import { extractErrorMessage } from '@/config/func' import { extractErrorMessage } from '@/config/func'
import { ArrowRight2 } from 'iconsax-react'
const NewRequest: FC = () => { const NewRequest: FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { mutate: submitRequest, isPending } = useSubmitRequest() const { mutate: submitRequest, isPending } = useSubmitRequest()
const [showRequests, setShowRequests] = useState([0])
const { items, setItems } = useRequestStore() const { items, setItems } = useRequestStore()
const [editingIndex, setEditingIndex] = useState<number | null>(null)
const [formKey, setFormKey] = useState(0)
const onSubmit = () => { useEffect(() => {
if (items.length > 0) { setItems([])
submitRequest(items, { return () => setItems([])
onSuccess: () => { }, [setItems])
setItems([])
setShowRequests([0]) const handleSaved = () => {
toast('سفارش با موفقیت ثبت شد', 'success') const wasEditing = editingIndex !== null
navigate(Paths.myRequests, { state: { refresh: true } }) setEditingIndex(null)
}, setFormKey((k) => k + 1)
onError: (error) => { toast(wasEditing ? 'قلم ویرایش شد' : 'قلم به لیست اضافه شد', 'success')
toast(extractErrorMessage(error), 'error') }
}
}) const handleRemove = (index: number) => {
} else { const next = items.filter((_, i) => i !== index)
toast('حداقل یک سفارش باید اضافه کنید.', 'error') setItems(next)
if (editingIndex === index) {
setEditingIndex(null)
setFormKey((k) => k + 1)
} else if (editingIndex !== null && editingIndex > index) {
setEditingIndex(editingIndex - 1)
} }
} }
const handleEdit = (index: number) => {
setEditingIndex(index)
}
const onSubmit = () => {
if (items.length === 0) {
toast('حداقل یک قلم باید به درخواست اضافه کنید.', 'error')
return
}
submitRequest(items, {
onSuccess: () => {
setItems([])
setEditingIndex(null)
setFormKey((k) => k + 1)
toast('سفارش با موفقیت ثبت شد', 'success')
navigate(Paths.myRequests, { state: { refresh: true } })
},
onError: (error) => {
toast(extractErrorMessage(error), 'error')
},
})
}
return ( return (
<div className='mt-5'> <div className='mt-4 pb-12'>
<div className='justify-between items-center flex'> <div className='flex items-center justify-between gap-4'>
<h1 className='text-lg font-light'> <h1 className='text-lg font-light'>درخواست جدید</h1>
درخواست جدید <Link
</h1> to={Paths.myRequests}
<Button className='flex items-center gap-1 text-sm text-description hover:text-black shrink-0'
className='w-fit px-5'
onClick={onSubmit}
isLoading={isPending}
> >
<div className='flex gap-2 items-center'> <ArrowRight2 size={18} color='currentColor' />
<TickSquare size={20} color='black' /> بازگشت به لیست
<div>ثبت نهایی درخواست</div> </Link>
</div>
</Button>
</div> </div>
{showRequests.map((item) => { <p className='text-description text-xs mt-2 leading-5'>
return ( برای هر محصول یک قلم اضافه کنید، سپس درخواست را ثبت نهایی کنید.
<Request addNewItem={() => setShowRequests([...showRequests, showRequests.length])} key={item} /> </p>
)
})}
<div className='flex flex-col-reverse xl:flex-row gap-6 xl:mt-8 mt-4'>
<div className='flex-1 min-w-0'>
<Request
key={editingIndex !== null ? `edit-${editingIndex}` : `new-${formKey}`}
editIndex={editingIndex}
initialItem={
editingIndex !== null ? items[editingIndex] : undefined
}
onSaved={handleSaved}
onCancelEdit={
editingIndex !== null
? () => {
setEditingIndex(null)
setFormKey((k) => k + 1)
}
: undefined
}
/>
</div>
<RequestItemsList
items={items}
editingIndex={editingIndex}
onEdit={handleEdit}
onRemove={handleRemove}
onSubmit={onSubmit}
isPending={isPending}
/>
</div>
</div> </div>
) )
} }
@@ -19,16 +19,20 @@ const ManageAttribute: FC<Props> = (props) => {
const { attributes, formik } = props const { attributes, formik } = props
const getAttributeValue = (fieldId: string) => {
const attr = formik.values.attributes.find((o) => o.fieldId === fieldId)
return attr?.value != null ? String(attr.value) : ''
}
const handleChange = (attributeId: string, value: string) => { const handleChange = (attributeId: string, value: string) => {
const attribute = formik.values.attributes const next = [...formik.values.attributes]
const index: number = attribute.findIndex(o => o.fieldId === attributeId) const index = next.findIndex((o) => o.fieldId === attributeId)
if (index > -1) { if (index > -1) {
attribute[index].value = value next[index] = { ...next[index], value }
} else { } else {
attribute.push({ fieldId: attributeId, value: value }) next.push({ fieldId: attributeId, value })
} }
formik.setFieldValue('attributes', attribute) formik.setFieldValue('attributes', next)
} }
@@ -44,6 +48,7 @@ const ManageAttribute: FC<Props> = (props) => {
<Select <Select
placeholder={`انتخاب ${item.isRequired ? '(اجباری)' : '(اختاری)'}`} placeholder={`انتخاب ${item.isRequired ? '(اجباری)' : '(اختاری)'}`}
label={item.name} label={item.name}
value={getAttributeValue(item.id)}
items={item.options?.map((value) => { items={item.options?.map((value) => {
return { return {
label: value.value, label: value.value,
@@ -100,7 +105,9 @@ const ManageAttribute: FC<Props> = (props) => {
return ( return (
<div key={item.id} className='mt-6'> <div key={item.id} className='mt-6'>
<DatePickerComponent <DatePickerComponent
key={`${item.id}-${getAttributeValue(item.id)}`}
onChange={(date) => handleChange(item.id, date)} onChange={(date) => handleChange(item.id, date)}
defaultValue={getAttributeValue(item.id) || undefined}
placeholder='' placeholder=''
label={item.name} label={item.name}
/> />
@@ -112,6 +119,7 @@ const ManageAttribute: FC<Props> = (props) => {
<Input <Input
label={item.name} label={item.name}
type={item.type} type={item.type}
value={getAttributeValue(item.id)}
onChange={(e) => handleChange(item.id, e.target.value)} onChange={(e) => handleChange(item.id, e.target.value)}
/> />
</div> </div>
@@ -121,6 +129,7 @@ const ManageAttribute: FC<Props> = (props) => {
<div key={item.id} className='mt-6'> <div key={item.id} className='mt-6'>
<Textarea <Textarea
label={item.name} label={item.name}
value={getAttributeValue(item.id)}
onChange={(e) => handleChange(item.id, e.target.value)} onChange={(e) => handleChange(item.id, e.target.value)}
/> />
</div> </div>
+142 -76
View File
@@ -1,11 +1,11 @@
import { useState, type ChangeEvent, type FC } from 'react' import { useEffect, useMemo, useState, type ChangeEvent, type FC } from 'react'
import Button from '@/components/Button' import Button from '@/components/Button'
import Input from '@/components/Input' import Input from '@/components/Input'
import Select from '@/components/Select' import Select from '@/components/Select'
import UploadBox from '@/components/UploadBox' import UploadBox from '@/components/UploadBox'
import VoiceRecorder from '@/components/VoiceRecorder' import VoiceRecorder from '@/components/VoiceRecorder'
import { COLORS } from '@/constants/colors' import { COLORS } from '@/constants/colors'
import { AddSquare, Edit } from 'iconsax-react' import { AddSquare, CloseCircle, Edit } from 'iconsax-react'
import ProductsSelect from './ProductsSelect' import ProductsSelect from './ProductsSelect'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
@@ -16,49 +16,53 @@ import { useMultiUpload, useSingleUpload } from '@/pages/uploader/hooks/useUploa
import { useRequestStore } from '../store/RequestStore' import { useRequestStore } from '../store/RequestStore'
import { clx } from '@/helpers/utils' import { clx } from '@/helpers/utils'
type Props = { const emptyValues: RequestType = {
addNewItem: () => void, productId: undefined,
attachments: [],
quantity: undefined,
description: '',
attributes: [],
} }
const Request: FC<Props> = ({ addNewItem }) => { type Props = {
editIndex: number | null
initialItem?: RequestType
onSaved: () => void
onCancelEdit?: () => void
}
const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) => {
const isEditing = editIndex !== null
const { data } = useGetProducts() const { data } = useGetProducts()
const { setItems, items } = useRequestStore() const { setItems, items } = useRequestStore()
const [indexRequest, setIndexRequest] = useState<number>(-1)
const [isEditMode, setIsEditMode] = useState<boolean>(false)
const [productSelected, setProductSelected] = useState<ProductType>() const [productSelected, setProductSelected] = useState<ProductType>()
/** مقدار انتخاب‌شده در دراپ‌داون تعداد؛ '0' = انتخاب دلخواه تا Input قابل ویرایش بماند */
const [quantitySelect, setQuantitySelect] = useState<string>('') const [quantitySelect, setQuantitySelect] = useState<string>('')
const [voiceFile, setVoiceFile] = useState<File>() const [voiceFile, setVoiceFile] = useState<File>()
const [files, setFiles] = useState<File[]>([]) const [files, setFiles] = useState<File[]>([])
const { data: attributes } = useGetAttributes(productSelected?.id)
const singleUpload = useSingleUpload() const singleUpload = useSingleUpload()
const multiUpload = useMultiUpload() const multiUpload = useMultiUpload()
const formik = useFormik<RequestType>({ const formik = useFormik<RequestType>({
initialValues: { initialValues: initialItem ?? emptyValues,
productId: undefined, enableReinitialize: true,
attachments: [],
quantity: undefined,
description: '',
attributes: []
},
validationSchema: Yup.object({ validationSchema: Yup.object({
productId: Yup.string().required('این فیلد اجباری می باشد'), productId: Yup.string().required('این فیلد اجباری می باشد'),
quantity: Yup.number().required('این فیلد اجباری می باشد'), quantity: Yup.number().required('این فیلد اجباری می باشد'),
}), }),
onSubmit: async (values) => { onSubmit: async (values) => {
const attachments: AttachmentsType[] = [] const attachments: AttachmentsType[] = [...(values.attachments ?? [])]
if (files.length) { if (files.length) {
await multiUpload.mutateAsync(files, { await multiUpload.mutateAsync(files, {
onSuccess: (data) => { onSuccess: (data) => {
data?.data?.map((item) => { data?.data?.forEach((item) => {
attachments.push({ attachments.push({
type: 'uploads_attach', type: 'uploads_attach',
url: item.url url: item.url,
}) })
}) })
} },
}) })
} }
if (voiceFile) { if (voiceFile) {
@@ -66,57 +70,110 @@ const Request: FC<Props> = ({ addNewItem }) => {
onSuccess: (data) => { onSuccess: (data) => {
attachments.push({ attachments.push({
type: 'voice', type: 'voice',
url: data?.data?.url url: data?.data?.url,
}) })
} },
}) })
} }
if (indexRequest === -1) {
values.attachments = attachments
setIndexRequest(items.length)
setItems([...items, values])
addNewItem()
setIsEditMode(true)
} else {
if (values.attachments.length) {
values.attachments = attachments
} const payload: RequestType = { ...values, attachments }
const items_edit = [...items]
items_edit[indexRequest] = values if (isEditing && editIndex !== null) {
setItems(items_edit) const updated = [...items]
updated[editIndex] = payload
setItems(updated)
} else {
setItems([...items, payload])
} }
}
setFiles([])
setVoiceFile(undefined)
onSaved()
},
}) })
const handleChange = (e: ChangeEvent<HTMLSelectElement>) => { const activeProductId = formik.values.productId ?? initialItem?.productId
const productId: string = e.target.value
const product = data?.data?.find(o => o.id === productId) const resolvedProduct = useMemo(() => {
if (productSelected) return productSelected
if (!activeProductId || !data?.data) return undefined
const product = data.data.find((p) => p.id === activeProductId)
if (!product) return undefined
const quantities = [...product.quantities]
if (quantities[0] !== 0) quantities.unshift(0)
return { ...product, quantities }
}, [productSelected, activeProductId, data])
const { data: attributes } = useGetAttributes(resolvedProduct?.id)
useEffect(() => {
if (!initialItem?.productId || !data?.data) return
const product = data.data.find((p) => p.id === initialItem.productId)
if (!product) return
const quantities = [...product.quantities]
if (quantities[0] !== 0) quantities.unshift(0)
setProductSelected({ ...product, quantities })
const qty = initialItem.quantity
if (qty !== undefined && quantities.includes(qty)) {
setQuantitySelect(String(qty))
} else {
setQuantitySelect('0')
}
}, [initialItem, data])
const handleProductChange = (e: ChangeEvent<HTMLSelectElement>) => {
const productId = e.target.value
const product = data?.data?.find((o) => o.id === productId)
if (product) { if (product) {
if (product.quantities[0] !== 0) { const quantities = [...product.quantities]
product.quantities.unshift(0) if (quantities[0] !== 0) quantities.unshift(0)
} setProductSelected({ ...product, quantities })
setProductSelected(product)
formik.setFieldValue('productId', productId) formik.setFieldValue('productId', productId)
formik.setFieldValue('attributes', [])
setQuantitySelect('') setQuantitySelect('')
} }
} }
const isUploading = singleUpload.isPending || multiUpload.isPending
return ( return (
<div className='bg-white rounded-3xl p-6 mt-8'> <div className='bg-white rounded-3xl p-6'>
<div className='font-light'>اطلاعات درخواست</div> <div className='flex items-center justify-between'>
<div className='font-light'>
{isEditing ? 'ویرایش قلم' : 'افزودن قلم جدید'}
</div>
{isEditing && onCancelEdit && (
<button
type='button'
onClick={onCancelEdit}
className='flex items-center gap-1 text-xs text-description hover:text-black'
>
<CloseCircle size={18} color='currentColor' />
انصراف
</button>
)}
</div>
<div className='mt-6 rowTwoInput'> <div className='mt-6 rowTwoInput'>
<ProductsSelect <ProductsSelect
onChange={handleChange} value={formik.values.productId ?? ''}
error_text={formik.touched.productId && formik.errors.productId ? formik.errors.productId : ''} onChange={handleProductChange}
error_text={
formik.touched.productId && formik.errors.productId
? formik.errors.productId
: ''
}
/> />
</div> </div>
<div> <div>
<ManageAttribute <ManageAttribute
key={`${resolvedProduct?.id ?? 'none'}-${editIndex ?? 'new'}`}
attributes={attributes?.data} attributes={attributes?.data}
formik={formik} formik={formik}
/> />
@@ -124,12 +181,12 @@ const Request: FC<Props> = ({ addNewItem }) => {
<div className='mt-6 rowTwoInput'> <div className='mt-6 rowTwoInput'>
<Select <Select
items={productSelected?.quantities?.map((item) => { items={
return { resolvedProduct?.quantities?.map((item) => ({
label: item === 0 ? 'انتخاب دلخواه' : item + '', label: item === 0 ? 'انتخاب دلخواه' : String(item),
value: item + '' value: String(item),
} })) ?? []
}) || []} }
label='تعداد' label='تعداد'
placeholder='انتخاب' placeholder='انتخاب'
value={quantitySelect} value={quantitySelect}
@@ -142,7 +199,11 @@ const Request: FC<Props> = ({ addNewItem }) => {
}} }}
onBlur={formik.handleBlur} onBlur={formik.handleBlur}
name='quantity' name='quantity'
error_text={formik.touched.quantity && formik.errors.quantity ? formik.errors.quantity : ''} error_text={
formik.touched.quantity && formik.errors.quantity
? formik.errors.quantity
: ''
}
/> />
<Input <Input
@@ -150,49 +211,54 @@ const Request: FC<Props> = ({ addNewItem }) => {
readOnly={quantitySelect !== '0'} readOnly={quantitySelect !== '0'}
{...formik.getFieldProps('quantity')} {...formik.getFieldProps('quantity')}
type='number' type='number'
error_text={formik.touched.quantity && formik.errors.quantity ? formik.errors.quantity : ''} error_text={
formik.touched.quantity && formik.errors.quantity
? formik.errors.quantity
: ''
}
/> />
</div> </div>
<div className='mt-6'> <div className='mt-6'>
<VoiceRecorder <VoiceRecorder
value={formik.values.description ?? ''}
onChange={(value) => formik.setFieldValue('description', value)} onChange={(value) => formik.setFieldValue('description', value)}
label='پیام صوتی' label='پیام صوتی'
onRecordingComplete={(blob) => { onRecordingComplete={(blob) => {
const file = new File([blob], "recording.wav", { type: blob.type }); const file = new File([blob], 'recording.wav', { type: blob.type })
setVoiceFile(file); setVoiceFile(file)
}} }}
/> />
</div> </div>
<div className='mt-6'> <div className='mt-6'>
<UploadBox <UploadBox label='آپلود فایل' isMultiple onChange={setFiles} />
label='آپلود فایل'
isMultiple
onChange={setFiles}
/>
</div> </div>
{isEditing && (initialItem?.attachments?.length ?? 0) > 0 && files.length === 0 && (
<p className='text-description text-xs mt-3'>
{initialItem!.attachments.length} پیوست قبلی حفظ میشود. برای جایگزینی، فایل جدید آپلود کنید.
</p>
)}
<div className='mt-6 flex justify-end'> <div className='mt-6 flex justify-end'>
<Button <Button
className={clx( className={clx(
'bg-transparent border border-primary text-primary w-fit px-6', 'w-fit px-6',
isEditMode && 'bg-transparent border-[#3B82F6] text-[#3B82F6] ' isEditing
? 'bg-transparent border border-[#3B82F6] text-[#3B82F6]'
: 'bg-transparent border border-primary text-primary'
)} )}
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
isLoading={singleUpload.isPending || multiUpload.isPending} isLoading={isUploading}
> >
<div className='flex gap-1'> <div className='flex gap-1 items-center'>
{ {isEditing ? (
isEditMode ? <Edit color='#3B82F6' size={20} />
<Edit color='#3B82F6' size={20} /> ) : (
: <AddSquare color={COLORS.primary} size={20} />
<AddSquare color={COLORS.primary} size={20} /> )}
} <span>{isEditing ? 'ذخیره تغییرات' : 'افزودن به لیست'}</span>
<div>
{isEditMode ? 'ویرایش کردن' : 'اضافه کردن'}
</div>
</div> </div>
</Button> </Button>
</div> </div>
@@ -0,0 +1,114 @@
import { type FC } from 'react'
import Button from '@/components/Button'
import { Edit, ShoppingCart, TickSquare, Trash } from 'iconsax-react'
import { useGetProducts } from '../hooks/useRequestData'
import type { RequestType } from '../type/Types'
import { clx } from '@/helpers/utils'
type Props = {
items: RequestType[]
editingIndex: number | null
onEdit: (index: number) => void
onRemove: (index: number) => void
onSubmit: () => void
isPending: boolean
}
const RequestItemsList: FC<Props> = ({
items,
editingIndex,
onEdit,
onRemove,
onSubmit,
isPending,
}) => {
const { data: productsData } = useGetProducts()
const getProductTitle = (productId?: string) =>
productsData?.data?.find((p) => p.id === productId)?.title ?? 'محصول'
return (
<div className='bg-white w-full xl:w-[320px] shrink-0 py-6 px-5 h-fit rounded-3xl xl:sticky xl:top-6'>
<div className='flex items-center gap-2'>
<ShoppingCart size={20} color='black' />
<span className='text-sm font-medium'>اقلام درخواست</span>
{items.length > 0 && (
<span className='text-xs bg-primary/20 text-black rounded-full px-2 py-0.5 mr-auto'>
{items.length}
</span>
)}
</div>
{items.length === 0 ? (
<p className='text-description text-xs mt-6 leading-6'>
هنوز قلمی اضافه نشده. فرم را پر کنید و روی «افزودن به لیست» بزنید.
</p>
) : (
<ul className='mt-4 space-y-3 max-h-[360px] overflow-y-auto'>
{items.map((item, index) => (
<li
key={index}
className={clx(
'border rounded-2xl p-3 transition-colors',
editingIndex === index
? 'border-primary bg-primary/5'
: 'border-[#f0f2f8]'
)}
>
<div className='text-sm font-medium truncate'>
{getProductTitle(item.productId)}
</div>
<div className='text-description text-xs mt-1'>
تعداد: {item.quantity}
{item.attachments.length > 0 && (
<span className='mr-2'>
· {item.attachments.length} پیوست
</span>
)}
</div>
<div className='flex gap-2 mt-3'>
<button
type='button'
onClick={() => onEdit(index)}
className='flex items-center gap-1 text-xs text-[#3B82F6] hover:opacity-80'
>
<Edit size={16} color='#3B82F6' />
ویرایش
</button>
<button
type='button'
onClick={() => onRemove(index)}
className='flex items-center gap-1 text-xs text-red-500 hover:opacity-80 mr-auto'
>
<Trash size={16} color='#ef4444' />
حذف
</button>
</div>
</li>
))}
</ul>
)}
<div className='mt-6 pt-4 border-t border-[#f0f2f8]'>
<Button
className='w-full'
onClick={onSubmit}
isLoading={isPending}
disabled={items.length === 0}
>
<div className='flex gap-2 items-center justify-center'>
<TickSquare size={20} color='black' />
<span>ثبت نهایی درخواست</span>
</div>
</Button>
{items.length === 0 && (
<p className='text-description text-[10px] text-center mt-2'>
حداقل یک قلم برای ثبت لازم است
</p>
)}
</div>
</div>
)
}
export default RequestItemsList