new UX for new request
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import { type FC, useState, useMemo, useEffect } from 'react'
|
||||
import { useFormik } from 'formik'
|
||||
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 Select from '@/components/Select'
|
||||
import type { ItemsSelectType } from '@/components/Select'
|
||||
@@ -39,10 +41,12 @@ const PayInvoice: FC = () => {
|
||||
|
||||
const { data: user } = useGetMe()
|
||||
const { id } = useParams()
|
||||
const queryClient = useQueryClient()
|
||||
const { data, isPending } = useGetInvoiceDetail(id ?? '')
|
||||
const { mutate: payInvoice, isPending: isPaying } = usePayInvoice()
|
||||
const multiUpload = useMultiUpload()
|
||||
const [attachmentFiles, setAttachmentFiles] = useState<File[]>([])
|
||||
const [isPaymentDone, setIsPaymentDone] = useState(false)
|
||||
|
||||
const userCredit = user?.maxCredit ?? 0
|
||||
const methodItems = useMemo(() => getMethodItems(userCredit), [userCredit])
|
||||
@@ -103,13 +107,25 @@ const PayInvoice: FC = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: (res) => {
|
||||
onSuccess: (res, variables) => {
|
||||
const paymentUrl = res?.data?.paymentUrl
|
||||
if (paymentUrl) {
|
||||
toast('در حال انتقال به درگاه پرداخت', 'success')
|
||||
window.location.href = paymentUrl
|
||||
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')
|
||||
},
|
||||
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 (
|
||||
<div className="mt-4 text-sm">
|
||||
<div className="text-sm mb-6">پرداخت صورت حساب</div>
|
||||
|
||||
@@ -1,64 +1,115 @@
|
||||
import { useState, type FC } from 'react'
|
||||
import { useEffect, useState, type FC } from 'react'
|
||||
import Request from './components/Request'
|
||||
import Button from '@/components/Button'
|
||||
import { TickSquare } from 'iconsax-react'
|
||||
import RequestItemsList from './components/RequestItemsList'
|
||||
import { useRequestStore } from './store/RequestStore'
|
||||
import { useSubmitRequest } from './hooks/useRequestData'
|
||||
import { toast } from '@/shared/toast'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
import { Link, useNavigate } from 'react-router-dom'
|
||||
import { Paths } from '@/config/Paths'
|
||||
import { extractErrorMessage } from '@/config/func'
|
||||
import { ArrowRight2 } from 'iconsax-react'
|
||||
|
||||
const NewRequest: FC = () => {
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { mutate: submitRequest, isPending } = useSubmitRequest()
|
||||
const [showRequests, setShowRequests] = useState([0])
|
||||
const { items, setItems } = useRequestStore()
|
||||
const [editingIndex, setEditingIndex] = useState<number | null>(null)
|
||||
const [formKey, setFormKey] = useState(0)
|
||||
|
||||
const onSubmit = () => {
|
||||
if (items.length > 0) {
|
||||
submitRequest(items, {
|
||||
onSuccess: () => {
|
||||
setItems([])
|
||||
setShowRequests([0])
|
||||
toast('سفارش با موفقیت ثبت شد', 'success')
|
||||
navigate(Paths.myRequests, { state: { refresh: true } })
|
||||
},
|
||||
onError: (error) => {
|
||||
toast(extractErrorMessage(error), 'error')
|
||||
}
|
||||
})
|
||||
} else {
|
||||
toast('حداقل یک سفارش باید اضافه کنید.', 'error')
|
||||
useEffect(() => {
|
||||
setItems([])
|
||||
return () => setItems([])
|
||||
}, [setItems])
|
||||
|
||||
const handleSaved = () => {
|
||||
const wasEditing = editingIndex !== null
|
||||
setEditingIndex(null)
|
||||
setFormKey((k) => k + 1)
|
||||
toast(wasEditing ? 'قلم ویرایش شد' : 'قلم به لیست اضافه شد', 'success')
|
||||
}
|
||||
|
||||
const handleRemove = (index: number) => {
|
||||
const next = items.filter((_, i) => i !== index)
|
||||
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 (
|
||||
<div className='mt-5'>
|
||||
<div className='justify-between items-center flex'>
|
||||
<h1 className='text-lg font-light'>
|
||||
درخواست جدید
|
||||
</h1>
|
||||
<Button
|
||||
className='w-fit px-5'
|
||||
onClick={onSubmit}
|
||||
isLoading={isPending}
|
||||
<div className='mt-4 pb-12'>
|
||||
<div className='flex items-center justify-between gap-4'>
|
||||
<h1 className='text-lg font-light'>درخواست جدید</h1>
|
||||
<Link
|
||||
to={Paths.myRequests}
|
||||
className='flex items-center gap-1 text-sm text-description hover:text-black shrink-0'
|
||||
>
|
||||
<div className='flex gap-2 items-center'>
|
||||
<TickSquare size={20} color='black' />
|
||||
<div>ثبت نهایی درخواست</div>
|
||||
</div>
|
||||
</Button>
|
||||
<ArrowRight2 size={18} color='currentColor' />
|
||||
بازگشت به لیست
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{showRequests.map((item) => {
|
||||
return (
|
||||
<Request addNewItem={() => setShowRequests([...showRequests, showRequests.length])} key={item} />
|
||||
)
|
||||
})}
|
||||
<p className='text-description text-xs mt-2 leading-5'>
|
||||
برای هر محصول یک قلم اضافه کنید، سپس درخواست را ثبت نهایی کنید.
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,16 +19,20 @@ const ManageAttribute: FC<Props> = (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 attribute = formik.values.attributes
|
||||
const index: number = attribute.findIndex(o => o.fieldId === attributeId)
|
||||
const next = [...formik.values.attributes]
|
||||
const index = next.findIndex((o) => o.fieldId === attributeId)
|
||||
if (index > -1) {
|
||||
attribute[index].value = value
|
||||
next[index] = { ...next[index], value }
|
||||
} 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
|
||||
placeholder={`انتخاب ${item.isRequired ? '(اجباری)' : '(اختاری)'}`}
|
||||
label={item.name}
|
||||
value={getAttributeValue(item.id)}
|
||||
items={item.options?.map((value) => {
|
||||
return {
|
||||
label: value.value,
|
||||
@@ -100,7 +105,9 @@ const ManageAttribute: FC<Props> = (props) => {
|
||||
return (
|
||||
<div key={item.id} className='mt-6'>
|
||||
<DatePickerComponent
|
||||
key={`${item.id}-${getAttributeValue(item.id)}`}
|
||||
onChange={(date) => handleChange(item.id, date)}
|
||||
defaultValue={getAttributeValue(item.id) || undefined}
|
||||
placeholder=''
|
||||
label={item.name}
|
||||
/>
|
||||
@@ -112,6 +119,7 @@ const ManageAttribute: FC<Props> = (props) => {
|
||||
<Input
|
||||
label={item.name}
|
||||
type={item.type}
|
||||
value={getAttributeValue(item.id)}
|
||||
onChange={(e) => handleChange(item.id, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
@@ -121,6 +129,7 @@ const ManageAttribute: FC<Props> = (props) => {
|
||||
<div key={item.id} className='mt-6'>
|
||||
<Textarea
|
||||
label={item.name}
|
||||
value={getAttributeValue(item.id)}
|
||||
onChange={(e) => handleChange(item.id, e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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 Input from '@/components/Input'
|
||||
import Select from '@/components/Select'
|
||||
import UploadBox from '@/components/UploadBox'
|
||||
import VoiceRecorder from '@/components/VoiceRecorder'
|
||||
import { COLORS } from '@/constants/colors'
|
||||
import { AddSquare, Edit } from 'iconsax-react'
|
||||
import { AddSquare, CloseCircle, Edit } from 'iconsax-react'
|
||||
import ProductsSelect from './ProductsSelect'
|
||||
import { useFormik } from 'formik'
|
||||
import * as Yup from 'yup'
|
||||
@@ -16,49 +16,53 @@ import { useMultiUpload, useSingleUpload } from '@/pages/uploader/hooks/useUploa
|
||||
import { useRequestStore } from '../store/RequestStore'
|
||||
import { clx } from '@/helpers/utils'
|
||||
|
||||
type Props = {
|
||||
addNewItem: () => void,
|
||||
const emptyValues: RequestType = {
|
||||
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 { setItems, items } = useRequestStore()
|
||||
const [indexRequest, setIndexRequest] = useState<number>(-1)
|
||||
const [isEditMode, setIsEditMode] = useState<boolean>(false)
|
||||
const [productSelected, setProductSelected] = useState<ProductType>()
|
||||
/** مقدار انتخابشده در دراپداون تعداد؛ '0' = انتخاب دلخواه تا Input قابل ویرایش بماند */
|
||||
const [quantitySelect, setQuantitySelect] = useState<string>('')
|
||||
const [voiceFile, setVoiceFile] = useState<File>()
|
||||
const [files, setFiles] = useState<File[]>([])
|
||||
const { data: attributes } = useGetAttributes(productSelected?.id)
|
||||
const singleUpload = useSingleUpload()
|
||||
const multiUpload = useMultiUpload()
|
||||
|
||||
const formik = useFormik<RequestType>({
|
||||
initialValues: {
|
||||
productId: undefined,
|
||||
attachments: [],
|
||||
quantity: undefined,
|
||||
description: '',
|
||||
attributes: []
|
||||
},
|
||||
initialValues: initialItem ?? emptyValues,
|
||||
enableReinitialize: true,
|
||||
validationSchema: Yup.object({
|
||||
productId: Yup.string().required('این فیلد اجباری می باشد'),
|
||||
quantity: Yup.number().required('این فیلد اجباری می باشد'),
|
||||
}),
|
||||
onSubmit: async (values) => {
|
||||
const attachments: AttachmentsType[] = []
|
||||
const attachments: AttachmentsType[] = [...(values.attachments ?? [])]
|
||||
|
||||
if (files.length) {
|
||||
await multiUpload.mutateAsync(files, {
|
||||
onSuccess: (data) => {
|
||||
data?.data?.map((item) => {
|
||||
data?.data?.forEach((item) => {
|
||||
attachments.push({
|
||||
type: 'uploads_attach',
|
||||
url: item.url
|
||||
url: item.url,
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
if (voiceFile) {
|
||||
@@ -66,57 +70,110 @@ const Request: FC<Props> = ({ addNewItem }) => {
|
||||
onSuccess: (data) => {
|
||||
attachments.push({
|
||||
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 items_edit = [...items]
|
||||
items_edit[indexRequest] = values
|
||||
setItems(items_edit)
|
||||
const payload: RequestType = { ...values, attachments }
|
||||
|
||||
if (isEditing && editIndex !== null) {
|
||||
const updated = [...items]
|
||||
updated[editIndex] = payload
|
||||
setItems(updated)
|
||||
} else {
|
||||
setItems([...items, payload])
|
||||
}
|
||||
}
|
||||
|
||||
setFiles([])
|
||||
setVoiceFile(undefined)
|
||||
onSaved()
|
||||
},
|
||||
})
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLSelectElement>) => {
|
||||
const productId: string = e.target.value
|
||||
const product = data?.data?.find(o => o.id === productId)
|
||||
const activeProductId = formik.values.productId ?? initialItem?.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.quantities[0] !== 0) {
|
||||
product.quantities.unshift(0)
|
||||
}
|
||||
setProductSelected(product)
|
||||
const quantities = [...product.quantities]
|
||||
if (quantities[0] !== 0) quantities.unshift(0)
|
||||
setProductSelected({ ...product, quantities })
|
||||
formik.setFieldValue('productId', productId)
|
||||
formik.setFieldValue('attributes', [])
|
||||
setQuantitySelect('')
|
||||
}
|
||||
}
|
||||
|
||||
const isUploading = singleUpload.isPending || multiUpload.isPending
|
||||
|
||||
return (
|
||||
<div className='bg-white rounded-3xl p-6 mt-8'>
|
||||
<div className='font-light'>اطلاعات درخواست</div>
|
||||
<div className='bg-white rounded-3xl p-6'>
|
||||
<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'>
|
||||
<ProductsSelect
|
||||
onChange={handleChange}
|
||||
error_text={formik.touched.productId && formik.errors.productId ? formik.errors.productId : ''}
|
||||
value={formik.values.productId ?? ''}
|
||||
onChange={handleProductChange}
|
||||
error_text={
|
||||
formik.touched.productId && formik.errors.productId
|
||||
? formik.errors.productId
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ManageAttribute
|
||||
key={`${resolvedProduct?.id ?? 'none'}-${editIndex ?? 'new'}`}
|
||||
attributes={attributes?.data}
|
||||
formik={formik}
|
||||
/>
|
||||
@@ -124,12 +181,12 @@ const Request: FC<Props> = ({ addNewItem }) => {
|
||||
|
||||
<div className='mt-6 rowTwoInput'>
|
||||
<Select
|
||||
items={productSelected?.quantities?.map((item) => {
|
||||
return {
|
||||
label: item === 0 ? 'انتخاب دلخواه' : item + '',
|
||||
value: item + ''
|
||||
}
|
||||
}) || []}
|
||||
items={
|
||||
resolvedProduct?.quantities?.map((item) => ({
|
||||
label: item === 0 ? 'انتخاب دلخواه' : String(item),
|
||||
value: String(item),
|
||||
})) ?? []
|
||||
}
|
||||
label='تعداد'
|
||||
placeholder='انتخاب'
|
||||
value={quantitySelect}
|
||||
@@ -142,7 +199,11 @@ const Request: FC<Props> = ({ addNewItem }) => {
|
||||
}}
|
||||
onBlur={formik.handleBlur}
|
||||
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
|
||||
@@ -150,49 +211,54 @@ const Request: FC<Props> = ({ addNewItem }) => {
|
||||
readOnly={quantitySelect !== '0'}
|
||||
{...formik.getFieldProps('quantity')}
|
||||
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 className='mt-6'>
|
||||
<VoiceRecorder
|
||||
value={formik.values.description ?? ''}
|
||||
onChange={(value) => formik.setFieldValue('description', value)}
|
||||
label='پیام صوتی'
|
||||
onRecordingComplete={(blob) => {
|
||||
const file = new File([blob], "recording.wav", { type: blob.type });
|
||||
setVoiceFile(file);
|
||||
const file = new File([blob], 'recording.wav', { type: blob.type })
|
||||
setVoiceFile(file)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='mt-6'>
|
||||
<UploadBox
|
||||
label='آپلود فایل'
|
||||
isMultiple
|
||||
onChange={setFiles}
|
||||
/>
|
||||
<UploadBox label='آپلود فایل' isMultiple onChange={setFiles} />
|
||||
</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'>
|
||||
<Button
|
||||
className={clx(
|
||||
'bg-transparent border border-primary text-primary w-fit px-6',
|
||||
isEditMode && 'bg-transparent border-[#3B82F6] text-[#3B82F6] '
|
||||
'w-fit px-6',
|
||||
isEditing
|
||||
? 'bg-transparent border border-[#3B82F6] text-[#3B82F6]'
|
||||
: 'bg-transparent border border-primary text-primary'
|
||||
)}
|
||||
onClick={() => formik.handleSubmit()}
|
||||
isLoading={singleUpload.isPending || multiUpload.isPending}
|
||||
isLoading={isUploading}
|
||||
>
|
||||
<div className='flex gap-1'>
|
||||
{
|
||||
isEditMode ?
|
||||
<Edit color='#3B82F6' size={20} />
|
||||
:
|
||||
<AddSquare color={COLORS.primary} size={20} />
|
||||
}
|
||||
<div>
|
||||
{isEditMode ? 'ویرایش کردن' : 'اضافه کردن'}
|
||||
</div>
|
||||
<div className='flex gap-1 items-center'>
|
||||
{isEditing ? (
|
||||
<Edit color='#3B82F6' size={20} />
|
||||
) : (
|
||||
<AddSquare color={COLORS.primary} size={20} />
|
||||
)}
|
||||
<span>{isEditing ? 'ذخیره تغییرات' : 'افزودن به لیست'}</span>
|
||||
</div>
|
||||
</Button>
|
||||
</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
|
||||
Reference in New Issue
Block a user