refactor new req
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-21 12:44:16 +03:30
parent cd8677648e
commit 830e229cf3
5 changed files with 300 additions and 177 deletions
+33 -43
View File
@@ -22,8 +22,7 @@ const NewRequest: FC = () => {
const [formKey, setFormKey] = useState(0) const [formKey, setFormKey] = useState(0)
const [showConfirmModal, setShowConfirmModal] = useState(false) const [showConfirmModal, setShowConfirmModal] = useState(false)
const composerRef = useRef<ChatComposerHandle>(null) const composerRef = useRef<ChatComposerHandle>(null)
const [pendingNote, setPendingNote] = const [pendingNote, setPendingNote] = useState<ChatComposerSubmitPayload | null>(null)
useState<ChatComposerSubmitPayload | null>(null)
useEffect(() => { useEffect(() => {
setItems([]) setItems([])
@@ -49,9 +48,7 @@ const NewRequest: FC = () => {
} }
} }
const handleEdit = (index: number) => { const handleEdit = (index: number) => setEditingIndex(index)
setEditingIndex(index)
}
const openSubmitConfirm = () => { const openSubmitConfirm = () => {
if (items.length === 0) { if (items.length === 0) {
@@ -65,15 +62,11 @@ const NewRequest: FC = () => {
} }
if (composerRef.current?.hasUploadErrors()) { if (composerRef.current?.hasUploadErrors()) {
toast( toast('برخی فایل‌ها آپلود نشدند. آن‌ها را حذف یا دوباره انتخاب کنید', 'error')
'برخی فایل‌ها آپلود نشدند. آن‌ها را حذف یا دوباره انتخاب کنید',
'error',
)
return return
} }
const note = composerRef.current?.getPayload() ?? null setPendingNote(composerRef.current?.getPayload() ?? null)
setPendingNote(note)
setShowConfirmModal(true) setShowConfirmModal(true)
} }
@@ -82,9 +75,7 @@ const NewRequest: FC = () => {
{ {
items, items,
...(pendingNote?.content ? { description: pendingNote.content } : {}), ...(pendingNote?.content ? { description: pendingNote.content } : {}),
...(pendingNote?.attachments?.length ...(pendingNote?.attachments?.length ? { attachments: pendingNote.attachments } : {}),
? { attachments: pendingNote.attachments }
: {}),
}, },
{ {
onSuccess: () => { onSuccess: () => {
@@ -105,32 +96,31 @@ const NewRequest: FC = () => {
} }
return ( return (
<div className='mt-4 pb-12'> <div className='pb-16'>
<div className='flex items-center justify-between gap-4'> {/* Page header */}
<h1 className='text-lg font-light'>درخواست جدید</h1> <div className='flex items-center justify-between gap-4 mb-1'>
<h1 className='text-lg font-semibold'>درخواست جدید</h1>
<Link <Link
to={Paths.myRequests} to={Paths.myRequests}
className='flex items-center gap-1 text-sm text-description hover:text-black shrink-0' className='flex items-center gap-1 text-sm text-description hover:text-black transition-colors shrink-0'
> >
<ArrowRight2 size={18} color='currentColor' /> <ArrowRight2 size={18} color='currentColor' />
بازگشت به لیست بازگشت به لیست
</Link> </Link>
</div> </div>
<p className='text-description text-xs mt-2 leading-5'> <p className='text-description text-xs leading-6 mt-1 max-w-xl'>
برای هر محصول یک قلم اضافه کنید. در صورت نیاز توضیحات یا فایل را برای هر محصول یک قلم اضافه کنید. در صورت نیاز توضیحات یا فایل را در بخش پایین وارد کنید و از دکمه «ثبت نهایی درخواست» استفاده کنید.
در بخش پایین وارد کنید و از دکمه «ثبت نهایی درخواست» در کنار لیست
استفاده کنید.
</p> </p>
<div className='flex flex-col-reverse xl:flex-row gap-6 xl:mt-8 mt-4'> {/* Main content */}
<div className='flex-1 min-w-0'> <div className='flex flex-col xl:flex-row gap-6 mt-6'>
{/* Left column: form + notes */}
<div className='flex-1 min-w-0 flex flex-col gap-6'>
<Request <Request
key={editingIndex !== null ? `edit-${editingIndex}` : `new-${formKey}`} key={editingIndex !== null ? `edit-${editingIndex}` : `new-${formKey}`}
editIndex={editingIndex} editIndex={editingIndex}
initialItem={ initialItem={editingIndex !== null ? items[editingIndex] : undefined}
editingIndex !== null ? items[editingIndex] : undefined
}
onSaved={handleSaved} onSaved={handleSaved}
onCancelEdit={ onCancelEdit={
editingIndex !== null editingIndex !== null
@@ -141,8 +131,24 @@ const NewRequest: FC = () => {
: undefined : undefined
} }
/> />
<section className='bg-white rounded-3xl p-6'>
<h2 className='text-sm font-medium mb-1'>توضیحات درخواست</h2>
<p className='text-description text-xs mb-5 leading-6'>
توضیحات، فایل یا پیام صوتی خود را اینجا بنویسید.
</p>
<ChatComposer
ref={composerRef}
onSubmit={async () => undefined}
showSubmitButton={false}
label='پیام شما'
placeholder='توضیحات یا درخواست خود را بنویسید...'
allowEmptySubmit
/>
</section>
</div> </div>
{/* Right column: items list (sticky on xl) */}
<RequestItemsList <RequestItemsList
items={items} items={items}
editingIndex={editingIndex} editingIndex={editingIndex}
@@ -153,22 +159,6 @@ const NewRequest: FC = () => {
/> />
</div> </div>
<section className='bg-white rounded-3xl p-6 mt-6'>
<h2 className='text-sm font-light mb-1'>توضیحات درخواست</h2>
<p className='text-description text-xs mb-6 leading-5'>
توضیحات، فایل یا پیام صوتی خود را اینجا بنویسید. برای ارسال
درخواست از دکمه کنار لیست اقلام استفاده کنید.
</p>
<ChatComposer
ref={composerRef}
onSubmit={async () => undefined}
showSubmitButton={false}
label='پیام شما'
placeholder='توضیحات یا درخواست خود را بنویسید...'
allowEmptySubmit
/>
</section>
<ModalConfirm <ModalConfirm
open={showConfirmModal} open={showConfirmModal}
onClose={() => setShowConfirmModal(false)} onClose={() => setShowConfirmModal(false)}
@@ -0,0 +1,112 @@
import { type FC } from 'react'
import { clx } from '@/helpers/utils'
import PresignedImage from '@/components/PresignedImage'
import placeholderImage from '@/assets/images/placeholder-product.svg'
export type AvatarSelectionOption = {
id: string
title: string
imageUrl?: string
}
type Props = {
label: string
items: AvatarSelectionOption[]
selectedId?: string
onSelect: (id: string) => void
isLoading?: boolean
emptyMessage?: string
error_text?: string
}
const SKELETON_COUNT = 6
const AvatarSelectionGrid: FC<Props> = ({
label,
items,
selectedId,
onSelect,
isLoading = false,
emptyMessage = 'موردی یافت نشد',
error_text,
}) => {
return (
<div className='w-full'>
<label className='text-sm font-medium text-primary-content'>{label}</label>
<div className='mt-3 w-full overflow-x-auto scrollbar-thin scrollbar-thumb-gray-200 scrollbar-track-transparent pb-1'>
{isLoading ? (
<div className='flex flex-nowrap gap-3'>
{Array.from({ length: SKELETON_COUNT }).map((_, index) => (
<div key={index} className='flex w-[72px] shrink-0 flex-col items-center gap-2'>
<div className='size-14 animate-pulse rounded-full bg-gray-200' />
<div className='h-6 w-full animate-pulse rounded-md bg-gray-100' />
</div>
))}
</div>
) : items.length === 0 ? (
<p className='text-sm font-light text-[#7B7E8B] py-2'>{emptyMessage}</p>
) : (
<div className='flex flex-nowrap gap-3'>
{items.map((item) => {
const isSelected = selectedId === item.id
return (
<button
key={item.id}
type='button'
onClick={() => onSelect(item.id)}
className={clx(
'flex w-[72px] shrink-0 flex-col items-center gap-2 text-center rounded-2xl p-1.5 transition-all',
isSelected
? 'bg-primary/8'
: 'hover:bg-gray-50',
)}
>
<div
className={clx(
'size-14 overflow-hidden rounded-full border-2 bg-[#F5F7FC] transition-all',
isSelected
? 'border-primary shadow-sm shadow-primary/20'
: 'border-transparent',
)}
>
{item.imageUrl ? (
<PresignedImage
src={item.imageUrl}
alt={item.title}
className='size-full object-cover'
/>
) : (
<img
src={placeholderImage}
alt={item.title}
className='size-full object-cover'
/>
)}
</div>
<span
className={clx(
'line-clamp-2 w-full text-[11px] leading-4',
isSelected ? 'text-primary font-medium' : 'text-[#292D32] font-light',
)}
>
{item.title}
</span>
</button>
)
})}
</div>
)}
</div>
{error_text && (
<p className='mt-2 mr-1 text-right text-xs font-medium text-red-500'>
{error_text}
</p>
)}
</div>
)
}
export default AvatarSelectionGrid
+52 -53
View File
@@ -1,46 +1,39 @@
import { type ChangeEvent, type FC, type SelectHTMLAttributes, useEffect, useMemo, useState } from 'react' import { type ChangeEvent, type FC, type SelectHTMLAttributes, useEffect, useMemo, useState } from 'react'
import { useGetCategories, useGetProducts } from '../hooks/useRequestData' import { useGetCategories, useGetProducts } from '../hooks/useRequestData'
import Select from '@/components/Select'
import type { CategoryType, ProductType } from '../type/Types' import type { CategoryType, ProductType } from '../type/Types'
import { import {
extractList, extractList,
findCategoryById, findCategoryById,
findCategoryPath, findCategoryPath,
getCategoriesAtLevel, getCategoriesAtLevel,
getLevelsToShow,
} from '../utils/categoryUtils' } from '../utils/categoryUtils'
import AvatarSelectionGrid from './AvatarSelectionGrid'
type Props = { type Props = {
error_text?: string, error_text?: string
onProductSelect?: (product: ProductType | undefined) => void, onProductSelect?: (product: ProductType | undefined) => void
} & SelectHTMLAttributes<HTMLSelectElement> } & SelectHTMLAttributes<HTMLSelectElement>
const ProductsSelect: FC<Props> = (props) => { const ProductsSelect: FC<Props> = (props) => {
const { error_text, onProductSelect, value, onChange, ...rest } = props const { error_text, onProductSelect, value, onChange } = props
const [selectedPath, setSelectedPath] = useState<string[]>([]) const [selectedPath, setSelectedPath] = useState<string[]>([])
const { data: categoriesData } = useGetCategories() const { data: categoriesData, isLoading: isCategoriesLoading } = useGetCategories()
const categories = useMemo( const categories = useMemo(
() => extractList<CategoryType>(categoriesData), () => extractList<CategoryType>(categoriesData),
[categoriesData], [categoriesData],
) )
const levelsToShow = useMemo(() => { const levelsToShow = useMemo(
const levels = [0] () => getLevelsToShow(categories, selectedPath),
[categories, selectedPath],
for (let level = 0; level < selectedPath.length; level++) { )
const category = findCategoryById(categories, selectedPath[level])
if (category?.children?.length) {
levels.push(level + 1)
}
}
return levels
}, [categories, selectedPath])
const activeCategoryId = selectedPath.at(-1) ?? '' const activeCategoryId = selectedPath.at(-1) ?? ''
const canLoadProducts = !!activeCategoryId const canLoadProducts = !!activeCategoryId
const { data: productsData } = useGetProducts(activeCategoryId || undefined, { const { data: productsData, isLoading: isProductsLoading } = useGetProducts(activeCategoryId || undefined, {
enabled: canLoadProducts, enabled: canLoadProducts,
}) })
@@ -59,14 +52,7 @@ const ProductsSelect: FC<Props> = (props) => {
[resolveProductsData], [resolveProductsData],
) )
const productItems = useMemo( // Resolve initial selectedPath when editing an existing item
() => products.map((product) => ({
label: product.title,
value: product.id,
})),
[products],
)
useEffect(() => { useEffect(() => {
if (!value || selectedPath.length || !categories.length) return if (!value || selectedPath.length || !categories.length) return
@@ -83,6 +69,7 @@ const ProductsSelect: FC<Props> = (props) => {
setSelectedPath(path) setSelectedPath(path)
}, [value, resolveProducts, categories, selectedPath.length]) }, [value, resolveProducts, categories, selectedPath.length])
// Emit onProductSelect whenever the selected product changes
useEffect(() => { useEffect(() => {
if (!value) { if (!value) {
onProductSelect?.(undefined) onProductSelect?.(undefined)
@@ -101,47 +88,59 @@ const ProductsSelect: FC<Props> = (props) => {
} as ChangeEvent<HTMLSelectElement>) } as ChangeEvent<HTMLSelectElement>)
} }
const handleCategoryChange = (level: number, nextValue: string) => { const handleCategorySelect = (level: number, categoryId: string) => {
setSelectedPath((prev) => [...prev.slice(0, level), nextValue]) setSelectedPath((prev) => [...prev.slice(0, level), categoryId])
emitProductChange('') emitProductChange('')
// If the selected category has children, keep drilling; don't auto-select product
const category = findCategoryById(categories, categoryId)
if (category?.children?.length) return
} }
const handleProductChange = (e: ChangeEvent<HTMLSelectElement>) => { const handleProductSelect = (productId: string) => {
onChange?.(e) emitProductChange(productId)
const product = products.find((item) => item.id === productId)
const product = products.find((item) => item.id === e.target.value)
onProductSelect?.(product) onProductSelect?.(product)
} }
return ( return (
<div className='space-y-4'> <div className='space-y-6'>
{levelsToShow.map((level) => { {levelsToShow.map((level) => {
const items = getCategoriesAtLevel(categories, level, selectedPath).map((category) => ({ const levelCategories = getCategoriesAtLevel(categories, level, selectedPath)
label: category.title, const levelLabel = level === 0 ? 'دسته‌بندی' : 'زیر دسته‌بندی'
value: category.id,
}))
return ( return (
<Select <AvatarSelectionGrid
key={level} key={level}
items={items} label={levelLabel}
label={level === 0 ? 'دسته‌بندی' : 'زیر دسته‌بندی'} isLoading={isCategoriesLoading}
placeholder={level === 0 ? 'انتخاب دسته‌بندی' : 'انتخاب زیر دسته‌بندی'} items={levelCategories.map((cat) => ({
value={selectedPath[level] ?? ''} id: cat.id,
onChange={(e) => handleCategoryChange(level, e.target.value)} title: cat.title,
imageUrl: cat.avatarUrl,
}))}
selectedId={selectedPath[level]}
onSelect={(id) => handleCategorySelect(level, id)}
emptyMessage='دسته‌بندی یافت نشد'
/> />
) )
})} })}
<Select
items={productItems} {canLoadProducts && (
label='محصول' <AvatarSelectionGrid
placeholder='انتخاب محصول' label='محصول'
value={value} isLoading={isProductsLoading}
onChange={handleProductChange} items={products.map((product) => ({
error_text={error_text} id: product.id,
disabled={!activeCategoryId} title: product.title,
{...rest} imageUrl: product.images?.[0],
/> }))}
selectedId={typeof value === 'string' ? value : undefined}
onSelect={handleProductSelect}
error_text={error_text}
emptyMessage='محصولی برای این دسته‌بندی یافت نشد'
/>
)}
</div> </div>
) )
} }
+38 -31
View File
@@ -1,7 +1,7 @@
import { useCallback, useState, type ChangeEvent, type FC } from 'react' import { useCallback, useState, type ChangeEvent, type FC } from 'react'
import Button from '@/components/Button' import Button from '@/components/Button'
import { COLORS } from '@/constants/colors' import { COLORS } from '@/constants/colors'
import { AddSquare, CloseCircle, Edit } from 'iconsax-react' import { AddSquare, CloseCircle, Edit2 } 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'
@@ -25,7 +25,6 @@ type Props = {
const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) => { const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) => {
const isEditing = editIndex !== null const isEditing = editIndex !== null
const setItems = useRequestStore((state) => state.setItems) const setItems = useRequestStore((state) => state.setItems)
const [productSelected, setProductSelected] = useState<ProductType>() const [productSelected, setProductSelected] = useState<ProductType>()
@@ -45,14 +44,11 @@ const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) =
const items = useRequestStore.getState().items const items = useRequestStore.getState().items
setItems([...items, values]) setItems([...items, values])
} }
onSaved() onSaved()
}, },
}) })
const resolvedProduct = productSelected const { data: attributes } = useGetAttributes(productSelected?.id)
const { data: attributes } = useGetAttributes(resolvedProduct?.id)
const handleProductChange = useCallback( const handleProductChange = useCallback(
(e: ChangeEvent<HTMLSelectElement>) => { (e: ChangeEvent<HTMLSelectElement>) => {
@@ -68,7 +64,7 @@ const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) =
formik.setFieldValue('productId', productId) formik.setFieldValue('productId', productId)
formik.setFieldValue('attributes', []) formik.setFieldValue('attributes', [])
}, },
[formik] [formik],
) )
const handleProductSelect = useCallback((product: ProductType | undefined) => { const handleProductSelect = useCallback((product: ProductType | undefined) => {
@@ -77,23 +73,36 @@ const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) =
return ( return (
<div className='bg-white rounded-3xl p-6'> <div className='bg-white rounded-3xl p-6'>
<div className='flex items-center justify-between'> {/* Section header */}
<div className='font-light'> <div className='flex items-center justify-between mb-6'>
{isEditing ? 'ویرایش قلم' : 'افزودن قلم جدید'} <div className='flex items-center gap-2'>
<div
className={clx(
'size-8 rounded-full flex items-center justify-center text-white text-xs font-bold',
isEditing ? 'bg-blue-500' : 'bg-primary',
)}
>
{isEditing ? <Edit2 size={15} color='white' /> : <AddSquare size={15} color='white' />}
</div>
<span className='text-sm font-medium'>
{isEditing ? 'ویرایش قلم' : 'افزودن قلم جدید'}
</span>
</div> </div>
{isEditing && onCancelEdit && ( {isEditing && onCancelEdit && (
<button <button
type='button' type='button'
onClick={onCancelEdit} onClick={onCancelEdit}
className='flex items-center gap-1 text-xs text-description hover:text-black' className='flex items-center gap-1 text-xs text-description hover:text-black transition-colors'
> >
<CloseCircle size={18} color='currentColor' /> <CloseCircle size={16} color='currentColor' />
انصراف انصراف
</button> </button>
)} )}
</div> </div>
<div className='mt-6 rowTwoInput'> {/* Product selector */}
<div className='rowTwoInput'>
<ProductsSelect <ProductsSelect
value={formik.values.productId ?? ''} value={formik.values.productId ?? ''}
onChange={handleProductChange} onChange={handleProductChange}
@@ -106,32 +115,30 @@ const Request: FC<Props> = ({ editIndex, initialItem, onSaved, onCancelEdit }) =
/> />
</div> </div>
<div> {/* Attributes */}
<ManageAttribute <ManageAttribute
key={`${resolvedProduct?.id ?? 'none'}-${editIndex ?? 'new'}`} key={`${productSelected?.id ?? 'none'}-${editIndex ?? 'new'}`}
attributes={attributes?.data} attributes={attributes?.data}
formik={formik} formik={formik}
/> />
</div>
{/* Action button */}
<div className='mt-6 flex justify-end'> <div className='mt-6 flex justify-end'>
<Button <Button
className={clx( className={clx(
'w-fit px-6', 'w-fit px-8 gap-2',
isEditing isEditing
? 'bg-transparent border border-[#3B82F6] text-[#3B82F6]' ? 'bg-transparent border border-blue-500 text-blue-500 hover:bg-blue-50'
: 'bg-transparent border border-primary text-primary' : 'bg-transparent border border-primary text-primary hover:bg-primary/5',
)} )}
onClick={() => formik.handleSubmit()} onClick={() => formik.handleSubmit()}
> >
<div className='flex gap-1 items-center'> {isEditing ? (
{isEditing ? ( <Edit2 color='currentColor' size={18} />
<Edit color='#3B82F6' size={20} /> ) : (
) : ( <AddSquare color={COLORS.primary} size={18} />
<AddSquare color={COLORS.primary} size={20} /> )}
)} <span>{isEditing ? 'ذخیره تغییرات' : 'افزودن به لیست'}</span>
<span>{isEditing ? 'ذخیره تغییرات' : 'افزودن به لیست'}</span>
</div>
</Button> </Button>
</div> </div>
</div> </div>
@@ -1,6 +1,6 @@
import { type FC } from 'react' import { type FC } from 'react'
import Button from '@/components/Button' import Button from '@/components/Button'
import { Edit, ShoppingCart, TickSquare, Trash } from 'iconsax-react' import { Edit2, ShoppingCart, TickSquare, Trash } from 'iconsax-react'
import { useGetProducts } from '../hooks/useRequestData' import { useGetProducts } from '../hooks/useRequestData'
import type { RequestItemType } from '../type/Types' import type { RequestItemType } from '../type/Types'
import { clx } from '@/helpers/utils' import { clx } from '@/helpers/utils'
@@ -28,60 +28,75 @@ const RequestItemsList: FC<Props> = ({
productsData?.data?.find((p) => p.id === productId)?.title ?? 'محصول' productsData?.data?.find((p) => p.id === productId)?.title ?? 'محصول'
return ( 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='bg-white w-full xl:w-[340px] shrink-0 rounded-3xl xl:sticky xl:top-6 h-fit'>
<div className='flex items-center gap-2'> {/* Header */}
<ShoppingCart size={20} color='black' /> <div className='flex items-center gap-2 px-5 pt-5 pb-4 border-b border-[#f0f2f8]'>
<span className='text-sm font-medium'>اقلام درخواست</span> <ShoppingCart size={20} color='black' variant='Bold' />
<span className='text-sm font-semibold'>اقلام درخواست</span>
{items.length > 0 && ( {items.length > 0 && (
<span className='text-xs bg-primary/20 text-black rounded-full px-2 py-0.5 mr-auto'> <span className='mr-auto text-xs bg-primary/15 text-primary font-medium rounded-full px-2.5 py-0.5 tabular-nums'>
{items.length} {items.length} قلم
</span> </span>
)} )}
</div> </div>
{items.length === 0 ? ( {/* Items */}
<p className='text-description text-xs mt-6 leading-6'> <div className='px-5 py-4'>
هنوز قلمی اضافه نشده. فرم را پر کنید و روی «افزودن به لیست» بزنید. {items.length === 0 ? (
</p> <div className='flex flex-col items-center justify-center gap-3 py-8 text-center'>
) : ( <div className='size-12 rounded-full bg-gray-50 flex items-center justify-center'>
<ul className='mt-4 space-y-3 max-h-[360px] overflow-y-auto'> <ShoppingCart size={22} color='#c0c4d0' />
{items.map((item, index) => ( </div>
<li <p className='text-description text-xs leading-6 max-w-[200px]'>
key={index} هنوز قلمی اضافه نشده. فرم را پر کنید و روی «افزودن به لیست» بزنید.
className={clx( </p>
'border rounded-2xl p-3 transition-colors', </div>
editingIndex === index ) : (
? 'border-primary bg-primary/5' <ul className='space-y-2 max-h-[380px] overflow-y-auto -mx-1 px-1'>
: 'border-[#f0f2f8]' {items.map((item, index) => (
)} <li
> key={index}
<div className='text-sm font-medium truncate'> className={clx(
{getProductTitle(item.productId)} 'border rounded-2xl px-4 py-3 transition-all',
</div> editingIndex === index
<div className='flex gap-2 mt-3'> ? 'border-primary bg-primary/5 shadow-sm'
<button : 'border-[#eef0f6] hover:border-gray-300',
type='button' )}
onClick={() => onEdit(index)} >
className='flex items-center gap-1 text-xs text-[#3B82F6] hover:opacity-80' <div className='flex items-start justify-between gap-2'>
> <span className='text-sm font-medium truncate leading-6'>
<Edit size={16} color='#3B82F6' /> {getProductTitle(item.productId)}
ویرایش </span>
</button> <span className='text-[10px] text-description shrink-0 bg-gray-100 rounded-full px-2 py-0.5'>
<button #{index + 1}
type='button' </span>
onClick={() => onRemove(index)} </div>
className='flex items-center gap-1 text-xs text-red-500 hover:opacity-80 mr-auto' <div className='flex gap-3 mt-2'>
> <button
<Trash size={16} color='#ef4444' /> type='button'
حذف onClick={() => onEdit(index)}
</button> className='flex items-center gap-1 text-xs text-primary hover:opacity-75 transition-opacity'
</div> >
</li> <Edit2 size={14} color='currentColor' />
))} ویرایش
</ul> </button>
)} <button
type='button'
onClick={() => onRemove(index)}
className='flex items-center gap-1 text-xs text-red-500 hover:opacity-75 transition-opacity mr-auto'
>
<Trash size={14} color='currentColor' />
حذف
</button>
</div>
</li>
))}
</ul>
)}
</div>
<div className='mt-6 pt-4 border-t border-[#f0f2f8]'> {/* Submit footer */}
<div className='px-5 pb-5 pt-3 border-t border-[#f0f2f8]'>
<Button <Button
className='w-full' className='w-full'
onClick={onSubmit} onClick={onSubmit}
@@ -89,7 +104,7 @@ const RequestItemsList: FC<Props> = ({
disabled={items.length === 0} disabled={items.length === 0}
> >
<div className='flex gap-2 items-center justify-center'> <div className='flex gap-2 items-center justify-center'>
<TickSquare size={20} color='black' /> <TickSquare size={20} color='currentColor' />
<span>ثبت نهایی درخواست</span> <span>ثبت نهایی درخواست</span>
</div> </div>
</Button> </Button>