Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2d35a428b1 | |||
| aac1a4f81b | |||
| 282e001933 | |||
| 8ec3f307b0 | |||
| 4c240ac9a1 | |||
| d52ff4f07e | |||
| c7b4e77421 |
@@ -95,6 +95,11 @@ const OrderDetails: FC = () => {
|
|||||||
const isOrderPaid = order.status === OrderStatus.PAID
|
const isOrderPaid = order.status === OrderStatus.PAID
|
||||||
const isPreparing = order.status === OrderStatus.PREPARING
|
const isPreparing = order.status === OrderStatus.PREPARING
|
||||||
const isCanceled = order.status === OrderStatus.CANCELED
|
const isCanceled = order.status === OrderStatus.CANCELED
|
||||||
|
const canCompleteOrder = [
|
||||||
|
OrderStatus.DELIVERED_TO_WAITER,
|
||||||
|
OrderStatus.DELIVERED_TO_RECEPTIONIST,
|
||||||
|
OrderStatus.SHIPPED,
|
||||||
|
].includes(order.status as OrderStatus)
|
||||||
// بررسی وضعیت پرداخت از payments array یا paymentStatus
|
// بررسی وضعیت پرداخت از payments array یا paymentStatus
|
||||||
// ابتدا از paymentStatus استفاده میکنیم، اگر نبود از اولین payment در payments array استفاده میکنیم
|
// ابتدا از paymentStatus استفاده میکنیم، اگر نبود از اولین payment در payments array استفاده میکنیم
|
||||||
const paymentStatus = order.paymentStatus || order.payments?.[0]?.status
|
const paymentStatus = order.paymentStatus || order.payments?.[0]?.status
|
||||||
@@ -104,7 +109,11 @@ const OrderDetails: FC = () => {
|
|||||||
const isDineInOrCarDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DineIn || order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCar
|
const isDineInOrCarDelivery = order.deliveryMethod?.method === DeliveryMethodEnum.DineIn || order.deliveryMethod?.method === DeliveryMethodEnum.DeliveryCar
|
||||||
const isCustomerPickup = order.deliveryMethod?.method === DeliveryMethodEnum.CustomerPickup
|
const isCustomerPickup = order.deliveryMethod?.method === DeliveryMethodEnum.CustomerPickup
|
||||||
|
|
||||||
const showVerifyPaymentBox = (isCashPayment || isCreditCardPayment) && isPaymentPending && !isCanceled
|
const payment = order.payments?.[0]
|
||||||
|
const hasPaymentDescription = Boolean(payment?.description)
|
||||||
|
const hasPaymentAttachments = Boolean(payment?.attachments && payment.attachments.length > 0)
|
||||||
|
const showPaymentReceiptBox = isCreditCardPayment && (hasPaymentDescription || hasPaymentAttachments)
|
||||||
|
const showVerifyPaymentButton = (isCashPayment || isCreditCardPayment) && isPaymentPending && !isCanceled
|
||||||
|
|
||||||
// نمایش دکمه ارسال به آشپزخانه
|
// نمایش دکمه ارسال به آشپزخانه
|
||||||
// برای pendingPayment با پرداخت نقدی یا برای paid
|
// برای pendingPayment با پرداخت نقدی یا برای paid
|
||||||
@@ -185,6 +194,20 @@ const OrderDetails: FC = () => {
|
|||||||
setShowCancelModal(false)
|
setShowCancelModal(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCompleteOrder = () => {
|
||||||
|
changeOrderStatus(
|
||||||
|
{
|
||||||
|
orderId: order.id,
|
||||||
|
status: OrderStatus.COMPLETED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onError: (error) => {
|
||||||
|
toast.error(extractErrorMessage(error))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const handleRefund = (text?: string) => {
|
const handleRefund = (text?: string) => {
|
||||||
if (!text) return
|
if (!text) return
|
||||||
refundPayment({
|
refundPayment({
|
||||||
@@ -232,11 +255,12 @@ const OrderDetails: FC = () => {
|
|||||||
getPaymentStatusTextColor={getPaymentStatusTextColor}
|
getPaymentStatusTextColor={getPaymentStatusTextColor}
|
||||||
getPaymentStatusText={getPaymentStatusText}
|
getPaymentStatusText={getPaymentStatusText}
|
||||||
/>
|
/>
|
||||||
{showVerifyPaymentBox && (
|
{showPaymentReceiptBox && (
|
||||||
<PaymentVerificationBox
|
<PaymentVerificationBox
|
||||||
order={order}
|
order={order}
|
||||||
isVerifyingPayment={isVerifyingPayment}
|
isVerifyingPayment={isVerifyingPayment}
|
||||||
onVerifyPayment={handleVerifyPayment}
|
onVerifyPayment={handleVerifyPayment}
|
||||||
|
showVerifyButton={showVerifyPaymentButton}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<OrderActions
|
<OrderActions
|
||||||
@@ -248,11 +272,13 @@ const OrderDetails: FC = () => {
|
|||||||
isDineInOrCarDelivery={isDineInOrCarDelivery}
|
isDineInOrCarDelivery={isDineInOrCarDelivery}
|
||||||
isCustomerPickup={isCustomerPickup}
|
isCustomerPickup={isCustomerPickup}
|
||||||
isCanceled={isCanceled}
|
isCanceled={isCanceled}
|
||||||
|
canCompleteOrder={canCompleteOrder}
|
||||||
isChangingStatus={isChangingStatus}
|
isChangingStatus={isChangingStatus}
|
||||||
onSendToKitchen={handleSendToKitchen}
|
onSendToKitchen={handleSendToKitchen}
|
||||||
onDeliverToCourier={handleDeliverToCourier}
|
onDeliverToCourier={handleDeliverToCourier}
|
||||||
onDeliverToWaiter={handleDeliverToWaiter}
|
onDeliverToWaiter={handleDeliverToWaiter}
|
||||||
onDeliverToReceptionist={handleDeliverToReceptionist}
|
onDeliverToReceptionist={handleDeliverToReceptionist}
|
||||||
|
onCompleteOrder={handleCompleteOrder}
|
||||||
onCancelOrder={() => setShowCancelModal(true)}
|
onCancelOrder={() => setShowCancelModal(true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { Profile, SearchNormal, TickCircle, UserAdd } from 'iconsax-react'
|
|||||||
import type { FormikProps } from 'formik'
|
import type { FormikProps } from 'formik'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
import { clx } from '@/helpers/utils'
|
import { clx } from '@/helpers/utils'
|
||||||
|
import Button from '@/components/Button'
|
||||||
|
import DefaulModal from '@/components/DefaulModal'
|
||||||
import Input from '@/components/Input'
|
import Input from '@/components/Input'
|
||||||
import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData'
|
import { useSearchUserByPhone } from '@/pages/customers/hooks/useUsersData'
|
||||||
import type { UserWithAddresses } from '@/pages/customers/types/Types'
|
import type { UserWithAddresses } from '@/pages/customers/types/Types'
|
||||||
@@ -28,6 +30,7 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { mutate: searchUser, isPending: isSearching } = useSearchUserByPhone()
|
const { mutate: searchUser, isPending: isSearching } = useSearchUserByPhone()
|
||||||
const [searchState, setSearchState] = useState<CustomerSearchState>('idle')
|
const [searchState, setSearchState] = useState<CustomerSearchState>('idle')
|
||||||
|
const [isCustomerModalOpen, setIsCustomerModalOpen] = useState(false)
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
const phone = formik.values.userPhone.trim()
|
const phone = formik.values.userPhone.trim()
|
||||||
@@ -87,17 +90,14 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
: ''
|
: ''
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className='w-full bg-white rounded-4xl px-5 py-4 flex-shrink-0'>
|
<div className='w-full bg-white rounded-4xl px-5 py-4 flex-shrink-0'>
|
||||||
<div className='flex items-center justify-between gap-3 mb-3'>
|
<div className='flex items-center justify-between gap-3'>
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<div
|
<div
|
||||||
className={clx(
|
className={clx(
|
||||||
'w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 transition-colors',
|
'w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 transition-colors',
|
||||||
searchState === 'found'
|
searchState === 'found' ? 'bg-green-50' : 'bg-[#EEF0F7]',
|
||||||
? 'bg-green-50'
|
|
||||||
: searchState === 'not_found'
|
|
||||||
? 'bg-[#EEF0F7]'
|
|
||||||
: 'bg-[#EEF0F7]',
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{searchState === 'found' ? (
|
{searchState === 'found' ? (
|
||||||
@@ -109,7 +109,12 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className='text-sm font-light'>اطلاعات مشتری</div>
|
<div className='text-sm font-light flex items-center gap-2'>
|
||||||
|
اطلاعات مشتری
|
||||||
|
<span className='text-[11px] text-description bg-[#F8F9FC] rounded-full px-2 py-0.5'>
|
||||||
|
اختیاری
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
{searchState === 'found' && customerName && (
|
{searchState === 'found' && customerName && (
|
||||||
<div className='text-[11px] text-green-600 mt-0.5'>
|
<div className='text-[11px] text-green-600 mt-0.5'>
|
||||||
{customerName}
|
{customerName}
|
||||||
@@ -120,19 +125,33 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
مشتری جدید
|
مشتری جدید
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{searchState === 'idle' && (
|
||||||
|
<div className='text-[11px] text-description mt-0.5'>
|
||||||
|
برای این سفارش وارد کردن اطلاعات مشتری الزامی نیست
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
className='w-fit px-4 h-10'
|
||||||
|
onClick={() => setIsCustomerModalOpen(true)}
|
||||||
|
>
|
||||||
|
{searchState === 'idle' ? 'افزودن مشتری' : 'ویرایش مشتری'}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{searchState === 'found' && (
|
<DefaulModal
|
||||||
<span className='text-[11px] text-green-600 bg-green-50 rounded-full px-3 py-1 flex-shrink-0'>
|
open={isCustomerModalOpen}
|
||||||
مشتری یافت شد
|
close={() => setIsCustomerModalOpen(false)}
|
||||||
</span>
|
isHeader={true}
|
||||||
)}
|
title_header='اطلاعات مشتری'
|
||||||
{searchState === 'idle' && (
|
width={700}
|
||||||
<span className='text-[11px] text-description bg-[#F8F9FC] rounded-full px-3 py-1 flex-shrink-0 hidden sm:inline'>
|
>
|
||||||
ابتدا شماره را جستجو کنید
|
<div className='p-2'>
|
||||||
</span>
|
<div className='text-xs text-description mb-4'>
|
||||||
)}
|
اطلاعات مشتری اختیاری است و در صورت نیاز میتوانید ثبت کنید.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-[1.2fr_1fr_1fr] gap-3 xl:items-end'>
|
<div className='grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-[1.2fr_1fr_1fr] gap-3 xl:items-end'>
|
||||||
@@ -211,7 +230,19 @@ const CreateOrderCustomerSection: FC<CreateOrderCustomerSectionProps> = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-5 flex justify-end'>
|
||||||
|
<Button
|
||||||
|
type='button'
|
||||||
|
className='w-fit px-6'
|
||||||
|
onClick={() => setIsCustomerModalOpen(false)}
|
||||||
|
>
|
||||||
|
تایید
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaulModal>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ interface OrderSummaryData {
|
|||||||
} | null>
|
} | null>
|
||||||
subTotal: number
|
subTotal: number
|
||||||
deliveryFee: number
|
deliveryFee: number
|
||||||
|
packingFee: number
|
||||||
|
discountAmount: number
|
||||||
total: number
|
total: number
|
||||||
totalItems: number
|
totalItems: number
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,7 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='bg-white rounded-4xl p-5 flex-1 min-h-0 flex flex-col h-full'>
|
<div className='bg-white rounded-4xl p-4 flex-1 min-h-0 flex flex-col h-full'>
|
||||||
<div className='flex items-center justify-between flex-shrink-0'>
|
<div className='flex items-center justify-between flex-shrink-0'>
|
||||||
<div className='flex items-center gap-2 text-sm font-light'>
|
<div className='flex items-center gap-2 text-sm font-light'>
|
||||||
<ShoppingCart size={18} color='#000' />
|
<ShoppingCart size={18} color='#000' />
|
||||||
@@ -143,12 +145,12 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(customerName || formik.values.userPhone) && (
|
{(customerName || formik.values.userPhone) && (
|
||||||
<div className='mt-3 p-3 bg-[#F8F9FC] rounded-xl flex-shrink-0'>
|
<div className='mt-2 p-2.5 bg-[#F8F9FC] rounded-xl flex-shrink-0'>
|
||||||
<div className='text-xs font-medium truncate'>
|
<div className='text-[11px] font-medium truncate'>
|
||||||
{customerName || 'مشتری'}
|
{customerName || 'مشتری'}
|
||||||
</div>
|
</div>
|
||||||
{formik.values.userPhone && (
|
{formik.values.userPhone && (
|
||||||
<div className='text-[11px] text-description mt-0.5'>
|
<div className='text-[10px] text-description mt-0.5'>
|
||||||
{formik.values.userPhone}
|
{formik.values.userPhone}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -158,7 +160,7 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
onClick={() => setIsDeliveryModalOpen(true)}
|
onClick={() => setIsDeliveryModalOpen(true)}
|
||||||
className={`mt-3 w-full text-right p-3 rounded-xl border transition-colors flex-shrink-0 ${
|
className={`mt-2 w-full text-right p-2.5 rounded-xl border transition-colors flex-shrink-0 ${
|
||||||
isDeliverySelected && isPaymentSelected
|
isDeliverySelected && isPaymentSelected
|
||||||
? 'border-border hover:border-primary/40 hover:bg-[#F8F9FC]'
|
? 'border-border hover:border-primary/40 hover:bg-[#F8F9FC]'
|
||||||
: 'border-dashed border-primary/50 bg-primary/5 hover:bg-primary/10'
|
: 'border-dashed border-primary/50 bg-primary/5 hover:bg-primary/10'
|
||||||
@@ -178,7 +180,7 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
</p>
|
</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className='mt-2 space-y-2'>
|
<div className='mt-1.5 space-y-1.5'>
|
||||||
<div className='flex items-center gap-2 text-xs'>
|
<div className='flex items-center gap-2 text-xs'>
|
||||||
<Truck size={15} color='#8C90A3' className='flex-shrink-0' />
|
<Truck size={15} color='#8C90A3' className='flex-shrink-0' />
|
||||||
<span
|
<span
|
||||||
@@ -221,8 +223,8 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className='mt-4 pt-3 border-t border-border border-dashed flex-1 min-h-0 flex flex-col'>
|
<div className='mt-3 pt-2.5 border-t border-border border-dashed flex-1 min-h-0 flex flex-col'>
|
||||||
<div className='text-xs text-description mb-2 flex-shrink-0'>سبد سفارش</div>
|
<div className='text-xs text-description mb-1.5 flex-shrink-0'>سبد سفارش</div>
|
||||||
|
|
||||||
<div className='flex-1 min-h-0 overflow-y-auto relative'>
|
<div className='flex-1 min-h-0 overflow-y-auto relative'>
|
||||||
{orderSummary.lineItems.length === 0 ? (
|
{orderSummary.lineItems.length === 0 ? (
|
||||||
@@ -233,7 +235,7 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='space-y-2.5 pb-2'>
|
<div className='space-y-2 pb-2'>
|
||||||
{orderSummary.lineItems.map((item) => {
|
{orderSummary.lineItems.map((item) => {
|
||||||
if (!item) return null
|
if (!item) return null
|
||||||
return (
|
return (
|
||||||
@@ -319,7 +321,51 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-3 space-y-2 text-xs pt-3 border-t border-border flex-shrink-0'>
|
<div className='mt-2 space-y-2 text-xs pt-2.5 border-t border-border flex-shrink-0'>
|
||||||
|
<div className='grid grid-cols-2 gap-2.5 mb-2'>
|
||||||
|
<div>
|
||||||
|
<div className='text-xs mb-1.5'>
|
||||||
|
<span className='font-medium'>هزینه بستهبندی</span>
|
||||||
|
<span className='text-description mr-1'>(اختیاری)</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
name='packingFee'
|
||||||
|
type='number'
|
||||||
|
inputMode='numeric'
|
||||||
|
min={0}
|
||||||
|
placeholder='تومان'
|
||||||
|
className='w-full h-10 rounded-xl border border-border px-3 text-sm outline-none focus:border-primary'
|
||||||
|
value={formik.values.packingFee || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
formik.setFieldValue(
|
||||||
|
'packingFee',
|
||||||
|
Math.max(Number(e.target.value) || 0, 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className='text-xs mb-1.5'>
|
||||||
|
<span className='font-medium'>تخفیف مبلغی</span>
|
||||||
|
<span className='text-description mr-1'>(اختیاری)</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
name='discountAmount'
|
||||||
|
type='number'
|
||||||
|
inputMode='numeric'
|
||||||
|
min={0}
|
||||||
|
placeholder='تومان'
|
||||||
|
className='w-full h-10 rounded-xl border border-border px-3 text-sm outline-none focus:border-primary'
|
||||||
|
value={formik.values.discountAmount || ''}
|
||||||
|
onChange={(e) =>
|
||||||
|
formik.setFieldValue(
|
||||||
|
'discountAmount',
|
||||||
|
Math.max(Number(e.target.value) || 0, 0)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<span className='text-description'>جمع آیتمها</span>
|
<span className='text-description'>جمع آیتمها</span>
|
||||||
<span>{formatPrice(orderSummary.subTotal)}</span>
|
<span>{formatPrice(orderSummary.subTotal)}</span>
|
||||||
@@ -330,6 +376,20 @@ const CreateOrderSidebar: FC<CreateOrderSidebarProps> = ({
|
|||||||
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
<span>{formatPrice(orderSummary.deliveryFee)}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{orderSummary.packingFee > 0 && (
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<span className='text-description'>هزینه بستهبندی</span>
|
||||||
|
<span>{formatPrice(orderSummary.packingFee)}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{orderSummary.discountAmount > 0 && (
|
||||||
|
<div className='flex justify-between'>
|
||||||
|
<span className='text-description'>تخفیف</span>
|
||||||
|
<span className='text-green-600'>
|
||||||
|
-{formatPrice(orderSummary.discountAmount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className='flex justify-between font-bold text-sm pt-2 border-t-2 border-gray-200'>
|
<div className='flex justify-between font-bold text-sm pt-2 border-t-2 border-gray-200'>
|
||||||
<span>جمع کل</span>
|
<span>جمع کل</span>
|
||||||
<span>{formatPrice(orderSummary.total)}</span>
|
<span>{formatPrice(orderSummary.total)}</span>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ interface OrderActionsProps {
|
|||||||
isDineInOrCarDelivery: boolean
|
isDineInOrCarDelivery: boolean
|
||||||
isCustomerPickup: boolean
|
isCustomerPickup: boolean
|
||||||
isCanceled: boolean
|
isCanceled: boolean
|
||||||
|
canCompleteOrder: boolean
|
||||||
isChangingStatus: boolean
|
isChangingStatus: boolean
|
||||||
onSendToKitchen: () => void
|
onSendToKitchen: () => void
|
||||||
onDeliverToCourier: () => void
|
onDeliverToCourier: () => void
|
||||||
onDeliverToWaiter: () => void
|
onDeliverToWaiter: () => void
|
||||||
onDeliverToReceptionist: () => void
|
onDeliverToReceptionist: () => void
|
||||||
|
onCompleteOrder: () => void
|
||||||
onCancelOrder: () => void
|
onCancelOrder: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,11 +29,13 @@ const OrderActions: FC<OrderActionsProps> = ({
|
|||||||
isDineInOrCarDelivery,
|
isDineInOrCarDelivery,
|
||||||
isCustomerPickup,
|
isCustomerPickup,
|
||||||
isCanceled,
|
isCanceled,
|
||||||
|
canCompleteOrder,
|
||||||
isChangingStatus,
|
isChangingStatus,
|
||||||
onSendToKitchen,
|
onSendToKitchen,
|
||||||
onDeliverToCourier,
|
onDeliverToCourier,
|
||||||
onDeliverToWaiter,
|
onDeliverToWaiter,
|
||||||
onDeliverToReceptionist,
|
onDeliverToReceptionist,
|
||||||
|
onCompleteOrder,
|
||||||
onCancelOrder
|
onCancelOrder
|
||||||
}) => {
|
}) => {
|
||||||
const [loadingAction, setLoadingAction] = useState<string | null>(null)
|
const [loadingAction, setLoadingAction] = useState<string | null>(null)
|
||||||
@@ -67,6 +71,11 @@ const OrderActions: FC<OrderActionsProps> = ({
|
|||||||
onCancelOrder()
|
onCancelOrder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCompleteOrder = () => {
|
||||||
|
setLoadingAction('completeOrder')
|
||||||
|
onCompleteOrder()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='mt-6 flex flex-col gap-3'>
|
<div className='mt-6 flex flex-col gap-3'>
|
||||||
<Button
|
<Button
|
||||||
@@ -111,6 +120,15 @@ const OrderActions: FC<OrderActionsProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{!isCanceled && canCompleteOrder && (
|
||||||
|
<Button
|
||||||
|
label='اتمام سفارش'
|
||||||
|
onClick={handleCompleteOrder}
|
||||||
|
isloading={isChangingStatus && loadingAction === 'completeOrder'}
|
||||||
|
className='bg-green-600 hover:bg-green-700'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{!isCanceled && showCancelButton && (
|
{!isCanceled && showCancelButton && (
|
||||||
<Button
|
<Button
|
||||||
label='لغو سفارش'
|
label='لغو سفارش'
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ interface PaymentVerificationBoxProps {
|
|||||||
order: Order
|
order: Order
|
||||||
isVerifyingPayment: boolean
|
isVerifyingPayment: boolean
|
||||||
onVerifyPayment: () => void
|
onVerifyPayment: () => void
|
||||||
|
showVerifyButton: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const PaymentVerificationBox: FC<PaymentVerificationBoxProps> = ({
|
const PaymentVerificationBox: FC<PaymentVerificationBoxProps> = ({
|
||||||
order,
|
order,
|
||||||
isVerifyingPayment,
|
isVerifyingPayment,
|
||||||
onVerifyPayment,
|
onVerifyPayment,
|
||||||
|
showVerifyButton,
|
||||||
}) => {
|
}) => {
|
||||||
const payment = order.payments?.[0]
|
const payment = order.payments?.[0]
|
||||||
const isCreditCard = order.paymentMethod?.method === PaymentMethodEnum.CreditCard
|
const isCreditCard = order.paymentMethod?.method === PaymentMethodEnum.CreditCard
|
||||||
@@ -53,12 +55,14 @@ const PaymentVerificationBox: FC<PaymentVerificationBoxProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showVerifyButton && (
|
||||||
<Button
|
<Button
|
||||||
label='تایید پرداخت'
|
label='تایید پرداخت'
|
||||||
onClick={onVerifyPayment}
|
onClick={onVerifyPayment}
|
||||||
isloading={isVerifyingPayment}
|
isloading={isVerifyingPayment}
|
||||||
className='bg-green-600 hover:bg-green-700 w-full'
|
className='bg-green-600 hover:bg-green-700 w-full'
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ export interface CreateOrderFormValues {
|
|||||||
items: CreateOrderItem[]
|
items: CreateOrderItem[]
|
||||||
deliveryMethodId: string
|
deliveryMethodId: string
|
||||||
paymentMethodId: string
|
paymentMethodId: string
|
||||||
|
discountAmount: number
|
||||||
|
packingFee: number
|
||||||
description: string
|
description: string
|
||||||
tableNumber: string
|
tableNumber: string
|
||||||
userAddress: CreateOrderUserAddress
|
userAddress: CreateOrderUserAddress
|
||||||
@@ -97,6 +99,8 @@ export const useCreateOrderForm = () => {
|
|||||||
items: [],
|
items: [],
|
||||||
deliveryMethodId: '',
|
deliveryMethodId: '',
|
||||||
paymentMethodId: '',
|
paymentMethodId: '',
|
||||||
|
discountAmount: 0,
|
||||||
|
packingFee: 0,
|
||||||
description: '',
|
description: '',
|
||||||
tableNumber: '',
|
tableNumber: '',
|
||||||
userAddress: emptyUserAddress(),
|
userAddress: emptyUserAddress(),
|
||||||
@@ -104,9 +108,9 @@ export const useCreateOrderForm = () => {
|
|||||||
paymentDesc: '',
|
paymentDesc: '',
|
||||||
},
|
},
|
||||||
validationSchema: Yup.object().shape({
|
validationSchema: Yup.object().shape({
|
||||||
userPhone: Yup.string().required('شماره تلفن الزامی است'),
|
userPhone: Yup.string(),
|
||||||
firstName: Yup.string().required('نام الزامی است'),
|
firstName: Yup.string(),
|
||||||
lastName: Yup.string().required('نام خانوادگی الزامی است'),
|
lastName: Yup.string(),
|
||||||
deliveryMethodId: Yup.string().required('روش تحویل الزامی است'),
|
deliveryMethodId: Yup.string().required('روش تحویل الزامی است'),
|
||||||
paymentMethodId: Yup.string().required('روش پرداخت الزامی است'),
|
paymentMethodId: Yup.string().required('روش پرداخت الزامی است'),
|
||||||
items: Yup.array()
|
items: Yup.array()
|
||||||
@@ -117,6 +121,8 @@ export const useCreateOrderForm = () => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.min(1, 'حداقل یک آیتم الزامی است'),
|
.min(1, 'حداقل یک آیتم الزامی است'),
|
||||||
|
discountAmount: Yup.number().min(0, 'تخفیف نمیتواند منفی باشد'),
|
||||||
|
packingFee: Yup.number().min(0, 'هزینه بستهبندی نمیتواند منفی باشد'),
|
||||||
}),
|
}),
|
||||||
onSubmit: async (values) => {
|
onSubmit: async (values) => {
|
||||||
const selectedDelivery = deliveryMethods.find(
|
const selectedDelivery = deliveryMethods.find(
|
||||||
@@ -154,12 +160,17 @@ export const useCreateOrderForm = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitOrder = (userId: string, addressId?: string) => {
|
const submitOrder = (userId?: string, addressId?: string) => {
|
||||||
const payload: CreateOrderType = {
|
const payload: CreateOrderType = {
|
||||||
userId,
|
|
||||||
items: values.items,
|
items: values.items,
|
||||||
deliveryMethodId: values.deliveryMethodId,
|
deliveryMethodId: values.deliveryMethodId,
|
||||||
paymentMethodId: values.paymentMethodId,
|
paymentMethodId: values.paymentMethodId,
|
||||||
|
discountAmount: values.discountAmount || 0,
|
||||||
|
packingFee: values.packingFee || 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userId) {
|
||||||
|
payload.userId = userId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.description.trim()) {
|
if (values.description.trim()) {
|
||||||
@@ -234,19 +245,24 @@ export const useCreateOrderForm = () => {
|
|||||||
|
|
||||||
const submitFlow = async () => {
|
const submitFlow = async () => {
|
||||||
try {
|
try {
|
||||||
|
const isCustomerRequiredForDelivery =
|
||||||
|
selectedDelivery?.method === DeliveryMethodEnum.DeliveryCourier
|
||||||
|
|
||||||
const userId = await registerNewCustomer()
|
const userId = await registerNewCustomer()
|
||||||
if (!userId) {
|
if (isCustomerRequiredForDelivery && !userId) {
|
||||||
toast.error('کاربر انتخاب نشده است')
|
toast.error('برای ثبت آدرس، انتخاب مشتری الزامی است')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let addressId: string | undefined
|
let addressId: string | undefined
|
||||||
|
if (userId) {
|
||||||
try {
|
try {
|
||||||
addressId = await resolveAddressId(userId)
|
addressId = await resolveAddressId(userId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(extractErrorMessage(error as ErrorType, 'خطا در ثبت آدرس'))
|
toast.error(extractErrorMessage(error as ErrorType, 'خطا در ثبت آدرس'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
selectedDelivery?.method === DeliveryMethodEnum.DeliveryCourier &&
|
selectedDelivery?.method === DeliveryMethodEnum.DeliveryCourier &&
|
||||||
@@ -383,16 +399,27 @@ export const useCreateOrderForm = () => {
|
|||||||
|
|
||||||
const subTotal = lineItems.reduce((sum, item) => sum + (item?.totalPrice || 0), 0)
|
const subTotal = lineItems.reduce((sum, item) => sum + (item?.totalPrice || 0), 0)
|
||||||
const deliveryFee = selectedDeliveryMethod?.deliveryFee || 0
|
const deliveryFee = selectedDeliveryMethod?.deliveryFee || 0
|
||||||
|
const packingFee = formik.values.packingFee || 0
|
||||||
|
const discountAmount = formik.values.discountAmount || 0
|
||||||
const totalItems = lineItems.reduce((sum, item) => sum + (item?.quantity || 0), 0)
|
const totalItems = lineItems.reduce((sum, item) => sum + (item?.quantity || 0), 0)
|
||||||
|
const total = Math.max(subTotal + deliveryFee + packingFee - discountAmount, 0)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lineItems,
|
lineItems,
|
||||||
subTotal,
|
subTotal,
|
||||||
deliveryFee,
|
deliveryFee,
|
||||||
total: subTotal + deliveryFee,
|
packingFee,
|
||||||
|
discountAmount,
|
||||||
|
total,
|
||||||
totalItems,
|
totalItems,
|
||||||
}
|
}
|
||||||
}, [formik.values.items, foodsMap, selectedDeliveryMethod])
|
}, [
|
||||||
|
formik.values.items,
|
||||||
|
formik.values.packingFee,
|
||||||
|
formik.values.discountAmount,
|
||||||
|
foodsMap,
|
||||||
|
selectedDeliveryMethod,
|
||||||
|
])
|
||||||
|
|
||||||
const addFoodToOrder = (foodId: string) => {
|
const addFoodToOrder = (foodId: string) => {
|
||||||
const existingIndex = formik.values.items.findIndex((item) => item.foodId === foodId)
|
const existingIndex = formik.values.items.findIndex((item) => item.foodId === foodId)
|
||||||
|
|||||||
@@ -265,10 +265,12 @@ export interface CreateOrderCarAddress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateOrderType {
|
export interface CreateOrderType {
|
||||||
userId: string;
|
userId?: string;
|
||||||
items: CreateOrderItem[];
|
items: CreateOrderItem[];
|
||||||
deliveryMethodId: string;
|
deliveryMethodId: string;
|
||||||
paymentMethodId: string;
|
paymentMethodId: string;
|
||||||
|
discountAmount: number;
|
||||||
|
packingFee: number;
|
||||||
description?: string;
|
description?: string;
|
||||||
tableNumber?: string;
|
tableNumber?: string;
|
||||||
addressId?: string;
|
addressId?: string;
|
||||||
|
|||||||
@@ -5,11 +5,45 @@ import { formatPrice } from "@/helpers/func";
|
|||||||
import { ArrowDown, ArrowUp, DocumentDownload } from "iconsax-react";
|
import { ArrowDown, ArrowUp, DocumentDownload } from "iconsax-react";
|
||||||
import moment from "moment-jalaali";
|
import moment from "moment-jalaali";
|
||||||
import { type FC, useMemo, useState } from "react";
|
import { type FC, useMemo, useState } from "react";
|
||||||
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts";
|
import { CartesianGrid, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts";
|
||||||
import { useGetPaymentsChart } from "../hooks/useStatsData";
|
import { useGetPaymentsChart } from "../hooks/useStatsData";
|
||||||
|
|
||||||
|
const PERSIAN_MONTHS = [
|
||||||
|
"فروردین",
|
||||||
|
"اردیبهشت",
|
||||||
|
"خرداد",
|
||||||
|
"تیر",
|
||||||
|
"مرداد",
|
||||||
|
"شهریور",
|
||||||
|
"مهر",
|
||||||
|
"آبان",
|
||||||
|
"آذر",
|
||||||
|
"دی",
|
||||||
|
"بهمن",
|
||||||
|
"اسفند",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const toPersianDigits = (value: string | number): string =>
|
||||||
|
String(value).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]);
|
||||||
|
|
||||||
|
const formatJalaliAxisLabel = (dateString: string, period: "daily" | "monthly" | "yearly"): string => {
|
||||||
|
const m = moment(dateString, "YYYY-MM-DD");
|
||||||
|
const jYear = m.jYear();
|
||||||
|
const jMonth = m.jMonth();
|
||||||
|
const jDate = m.jDate();
|
||||||
|
|
||||||
|
if (period === "yearly") return toPersianDigits(jYear);
|
||||||
|
if (period === "monthly") return `${PERSIAN_MONTHS[jMonth]} ${toPersianDigits(jYear)}`;
|
||||||
|
return toPersianDigits(`${String(jMonth + 1).padStart(2, "0")}/${String(jDate).padStart(2, "0")}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatJalaliFullDate = (dateString: string): string => {
|
||||||
|
const m = moment(dateString, "YYYY-MM-DD");
|
||||||
|
return `${toPersianDigits(m.jDate())} ${PERSIAN_MONTHS[m.jMonth()]} ${toPersianDigits(m.jYear())}`;
|
||||||
|
};
|
||||||
|
|
||||||
const RevenueChart: FC = () => {
|
const RevenueChart: FC = () => {
|
||||||
const [selectedTab] = useState<"daily" | "monthly" | "yearly">("monthly");
|
const [selectedTab] = useState<"daily" | "monthly" | "yearly">("daily");
|
||||||
const [startDate, setStartDate] = useState<string>("");
|
const [startDate, setStartDate] = useState<string>("");
|
||||||
const [endDate, setEndDate] = useState<string>("");
|
const [endDate, setEndDate] = useState<string>("");
|
||||||
|
|
||||||
@@ -33,38 +67,20 @@ const RevenueChart: FC = () => {
|
|||||||
|
|
||||||
const { data: paymentsChartData, isLoading } = useGetPaymentsChart(apiParams);
|
const { data: paymentsChartData, isLoading } = useGetPaymentsChart(apiParams);
|
||||||
|
|
||||||
const formatDateLabel = (dateString: string, period: "daily" | "monthly" | "yearly"): string => {
|
|
||||||
const date = new Date(dateString);
|
|
||||||
const options: Intl.DateTimeFormatOptions = {
|
|
||||||
year: "numeric",
|
|
||||||
};
|
|
||||||
if (period === "monthly") {
|
|
||||||
options.month = "long";
|
|
||||||
} else if (period === "daily") {
|
|
||||||
options.month = "short";
|
|
||||||
options.day = "numeric";
|
|
||||||
}
|
|
||||||
const persianDate = new Intl.DateTimeFormat("fa-IR", options).format(date);
|
|
||||||
return persianDate;
|
|
||||||
};
|
|
||||||
|
|
||||||
const chartData = useMemo(() => {
|
const chartData = useMemo(() => {
|
||||||
if (!paymentsChartData?.data) return [];
|
if (!paymentsChartData?.data) return [];
|
||||||
return paymentsChartData.data.map((item) => ({
|
return [...paymentsChartData.data]
|
||||||
month: formatDateLabel(item.date, selectedTab),
|
.sort((a, b) => moment(a.date, "YYYY-MM-DD").valueOf() - moment(b.date, "YYYY-MM-DD").valueOf())
|
||||||
|
.map((item) => ({
|
||||||
|
date: item.date,
|
||||||
|
label: formatJalaliAxisLabel(item.date, selectedTab),
|
||||||
|
tooltipLabel: formatJalaliFullDate(item.date),
|
||||||
online: item.online,
|
online: item.online,
|
||||||
cash: item.cash,
|
cash: item.cash,
|
||||||
}));
|
}));
|
||||||
}, [paymentsChartData?.data, selectedTab]);
|
}, [paymentsChartData?.data, selectedTab]);
|
||||||
|
|
||||||
const xAxisInterval = useMemo(() => {
|
const chartWidth = useMemo(() => Math.max(chartData.length * 48, 640), [chartData.length]);
|
||||||
const dataLength = chartData.length;
|
|
||||||
if (dataLength <= 7) return 0;
|
|
||||||
if (dataLength <= 15) return 1;
|
|
||||||
if (dataLength <= 30) return 2;
|
|
||||||
if (dataLength <= 60) return 4;
|
|
||||||
return Math.floor(dataLength / 12);
|
|
||||||
}, [chartData.length]);
|
|
||||||
|
|
||||||
const totalRevenue = useMemo(() => {
|
const totalRevenue = useMemo(() => {
|
||||||
return chartData.reduce((sum, item) => sum + item.online + item.cash, 0);
|
return chartData.reduce((sum, item) => sum + item.online + item.cash, 0);
|
||||||
@@ -94,49 +110,16 @@ const RevenueChart: FC = () => {
|
|||||||
const CustomTooltip = (props: {
|
const CustomTooltip = (props: {
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
payload?: Array<{
|
payload?: Array<{
|
||||||
dataKey?: string;
|
|
||||||
value?: number;
|
|
||||||
color?: string;
|
|
||||||
payload?: {
|
payload?: {
|
||||||
cash?: number;
|
cash: number;
|
||||||
online?: number;
|
online: number;
|
||||||
month?: string;
|
tooltipLabel: string;
|
||||||
};
|
};
|
||||||
}>;
|
}>;
|
||||||
label?: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
const { active, payload, label } = props;
|
const { active, payload } = props;
|
||||||
if (!active || !payload || payload.length === 0 || !label) {
|
const dataPoint = payload?.[0]?.payload;
|
||||||
return null;
|
if (!active || !dataPoint) {
|
||||||
}
|
|
||||||
|
|
||||||
// استفاده از paymentsChartData.data مستقیم برای دریافت دادههای واقعی
|
|
||||||
// پیدا کردن آخرین آیتمی که label آن با formatDateLabel match میکند
|
|
||||||
let dataPoint: { cash: number; online: number } | null = null;
|
|
||||||
|
|
||||||
if (paymentsChartData?.data) {
|
|
||||||
// پیدا کردن همه آیتمهای match شده و استفاده از آخرین
|
|
||||||
const matchingItems: Array<{ cash: number; online: number }> = [];
|
|
||||||
for (const item of paymentsChartData.data) {
|
|
||||||
const formattedLabel = formatDateLabel(item.date, selectedTab);
|
|
||||||
if (formattedLabel === label) {
|
|
||||||
matchingItems.push({ cash: item.cash, online: item.online });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (matchingItems.length > 0) {
|
|
||||||
dataPoint = matchingItems[matchingItems.length - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// اگر پیدا نکردیم، از chartData استفاده میکنیم
|
|
||||||
if (!dataPoint) {
|
|
||||||
const matchingItems = chartData.filter((item) => item.month === label);
|
|
||||||
if (matchingItems.length > 0) {
|
|
||||||
dataPoint = matchingItems[matchingItems.length - 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dataPoint) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +128,7 @@ const RevenueChart: FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-white p-3 rounded-xl shadow-lg border border-gray-200">
|
<div className="bg-white p-3 rounded-xl shadow-lg border border-gray-200">
|
||||||
<p className="text-sm font-medium text-gray-900 mb-2">{label}</p>
|
<p className="text-sm font-medium text-gray-900 mb-2">{dataPoint.tooltipLabel}</p>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="w-2 h-2 rounded-full bg-[#B0BAC9]" />
|
<div className="w-2 h-2 rounded-full bg-[#B0BAC9]" />
|
||||||
@@ -248,38 +231,26 @@ const RevenueChart: FC = () => {
|
|||||||
) : chartData.length === 0 ? (
|
) : chartData.length === 0 ? (
|
||||||
<div className="w-full h-[400px] min-h-[400px] flex items-center justify-center text-gray-500">دادهای برای نمایش وجود ندارد</div>
|
<div className="w-full h-[400px] min-h-[400px] flex items-center justify-center text-gray-500">دادهای برای نمایش وجود ندارد</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full h-[400px] min-h-[400px]">
|
<div className="w-full overflow-x-auto pb-2">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<div style={{ width: chartWidth, height: 420 }}>
|
||||||
<AreaChart data={chartData} margin={{ top: 10, right: 10, left: 10, bottom: 40 }}>
|
<LineChart data={chartData} width={chartWidth} height={420} margin={{ top: 10, right: 10, left: 10, bottom: 72 }}>
|
||||||
<defs>
|
|
||||||
<linearGradient id="colorOnline" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="5%" stopColor="#6B7FED" stopOpacity={0.3} />
|
|
||||||
<stop offset="95%" stopColor="#6B7FED" stopOpacity={0} />
|
|
||||||
</linearGradient>
|
|
||||||
<linearGradient id="colorCash" x1="0" y1="0" x2="0" y2="1">
|
|
||||||
<stop offset="5%" stopColor="#B0BAC9" stopOpacity={0.2} />
|
|
||||||
<stop offset="95%" stopColor="#B0BAC9" stopOpacity={0} />
|
|
||||||
</linearGradient>
|
|
||||||
</defs>
|
|
||||||
<CartesianGrid strokeDasharray="0" stroke="#f0f0f0" vertical={false} />
|
<CartesianGrid strokeDasharray="0" stroke="#f0f0f0" vertical={false} />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="month"
|
dataKey="label"
|
||||||
axisLine={false}
|
axisLine={false}
|
||||||
tickLine={false}
|
tickLine={false}
|
||||||
tick={{ fill: "#9CA3AF", fontSize: 11 }}
|
tick={{ fill: "#9CA3AF", fontSize: 10 }}
|
||||||
reversed
|
interval={0}
|
||||||
interval={xAxisInterval}
|
angle={-45}
|
||||||
angle={chartData.length > 15 ? -45 : 0}
|
textAnchor="end"
|
||||||
textAnchor={chartData.length > 15 ? "end" : "middle"}
|
height={72}
|
||||||
height={chartData.length > 15 ? 60 : 40}
|
|
||||||
minTickGap={10}
|
|
||||||
/>
|
/>
|
||||||
<YAxis orientation="right" axisLine={false} tickLine={false} tick={{ fill: "#9CA3AF", fontSize: 10 }} tickFormatter={formatYAxis} domain={[0, "dataMax + 2000000"]} tickMargin={50} />
|
<YAxis orientation="right" axisLine={false} tickLine={false} tick={{ fill: "#9CA3AF", fontSize: 10 }} tickFormatter={formatYAxis} domain={[0, "dataMax + 2000000"]} tickMargin={50} />
|
||||||
<Tooltip content={<CustomTooltip />} />
|
<Tooltip content={<CustomTooltip />} cursor={{ stroke: "#E5E7EB", strokeWidth: 1 }} />
|
||||||
<Area type="natural" dataKey="cash" stroke="#B0BAC9" strokeWidth={2} fillOpacity={1} fill="url(#colorCash)" />
|
<Line type="monotone" dataKey="cash" name="پرداخت نقدی" stroke="#B0BAC9" strokeWidth={2} dot={{ r: 3, fill: "#B0BAC9", strokeWidth: 0 }} activeDot={{ r: 5 }} />
|
||||||
<Area type="natural" dataKey="online" stroke="#6B7FED" strokeWidth={2} fillOpacity={1} fill="url(#colorOnline)" />
|
<Line type="monotone" dataKey="online" name="پرداخت آنلاین" stroke="#6B7FED" strokeWidth={2} dot={{ r: 3, fill: "#6B7FED", strokeWidth: 0 }} activeDot={{ r: 5 }} />
|
||||||
</AreaChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user