refcator create order
deploy to danak / build_and_deploy (push) Has been cancelled

This commit is contained in:
2026-07-17 18:50:07 +03:30
parent ac205ccb7a
commit d85e0e49ce
+216 -102
View File
@@ -1,13 +1,23 @@
import { useState, type FC } from 'react' import { useState, type FC, type ReactNode } from 'react'
import { useFormik } from 'formik' import { useFormik } from 'formik'
import * as Yup from 'yup' import * as Yup from 'yup'
import moment from 'moment-jalaali' import moment from 'moment-jalaali'
import { Link, useNavigate, useSearchParams } from 'react-router-dom' import { Link, useNavigate, useSearchParams } from 'react-router-dom'
import {
Box1,
Calendar,
DocumentText,
Profile2User,
ReceiptText,
TickSquare,
WalletMoney,
} from 'iconsax-react'
import Input from '@/components/Input' import Input from '@/components/Input'
import Textarea from '@/components/Textarea' import Textarea from '@/components/Textarea'
import Select from '@/components/Select' import Select from '@/components/Select'
import UploadBox from '@/components/UploadBox' import UploadBox from '@/components/UploadBox'
import Button from '@/components/Button' import Button from '@/components/Button'
import BackButton from '@/components/BackButton'
import { useMultiUpload } from '@/pages/uploader/hooks/useUploader' import { useMultiUpload } from '@/pages/uploader/hooks/useUploader'
import type { ConvertToOrderItemType } from './types/Types' import type { ConvertToOrderItemType } from './types/Types'
import { useConverToOrder, useGetProducts } from './hooks/useOrderData' import { useConverToOrder, useGetProducts } from './hooks/useOrderData'
@@ -24,6 +34,35 @@ import { Checkbox } from '@/components/ui/checkbox'
const formatAmount = (amount: number) => const formatAmount = (amount: number) =>
Number(amount).toLocaleString('fa-IR') + ' ریال' Number(amount).toLocaleString('fa-IR') + ' ریال'
type InfoFieldProps = {
label: string
value: ReactNode
icon?: ReactNode
}
const InfoField: FC<InfoFieldProps> = ({ label, value, icon }) => (
<div className="rounded-2xl border border-border/70 bg-[#FBFCFF] p-4">
<div className="mb-2 flex items-center gap-2 text-[12px] text-[#7D8597]">
{icon}
<span>{label}</span>
</div>
<div className="text-[15px] font-medium text-[#1F2937]">{value}</div>
</div>
)
type SummaryRowProps = {
label: string
value: string
valueClassName?: string
}
const SummaryRow: FC<SummaryRowProps> = ({ label, value, valueClassName }) => (
<div className="flex items-center justify-between rounded-xl border border-border/70 bg-white px-4 py-3">
<span className="text-[13px] text-[#6B7280]">{label}</span>
<span className={`text-[14px] font-semibold ${valueClassName ?? ''}`}>{value}</span>
</div>
)
const getInitialValues = (invoiceItemId: string | null): ConvertToOrderItemType => ({ const getInitialValues = (invoiceItemId: string | null): ConvertToOrderItemType => ({
invoiceItemId: invoiceItemId ?? '', invoiceItemId: invoiceItemId ?? '',
productId: '', productId: '',
@@ -46,11 +85,10 @@ const getValidationSchema = (invoiceItemId: string | null) =>
estimatedDays: Yup.number().required('تخمین روز اجباری است.').min(0, 'باید عدد مثبت باشد'), estimatedDays: Yup.number().required('تخمین روز اجباری است.').min(0, 'باید عدد مثبت باشد'),
title: Yup.string().required('عنوان اجباری است.'), title: Yup.string().required('عنوان اجباری است.'),
description: Yup.string(), description: Yup.string(),
designerId: Yup.string().required('این فیلد اجباری است') designerId: Yup.string().required('این فیلد اجباری است'),
}) })
const ConvertToOrders: FC = () => { const ConvertToOrders: FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams] = useSearchParams() const [searchParams] = useSearchParams()
const invoiceItemId = searchParams.get('invoiceItemId') const invoiceItemId = searchParams.get('invoiceItemId')
@@ -99,7 +137,7 @@ const ConvertToOrders: FC = () => {
}, },
onError: (error) => { onError: (error) => {
toast.error(extractErrorMessage(error)) toast.error(extractErrorMessage(error))
} },
}) })
}, },
}) })
@@ -110,94 +148,54 @@ const ConvertToOrders: FC = () => {
: invoiceItem?.invoice?.user?.phone ?? '-' : invoiceItem?.invoice?.user?.phone ?? '-'
const discounts = invoiceItem ? formatItemDiscountDisplay(invoiceItem) : null const discounts = invoiceItem ? formatItemDiscountDisplay(invoiceItem) : null
const isSubmitting = multiUpload.isPending || isPending
const backTo = invoiceItem?.invoice?.id
? Paths.perfomaInvoice.detail + invoiceItem.invoice.id
: Paths.order.list
return ( return (
<div className="mt-5"> <div className="mt-5 space-y-8">
<h1 className="text-lg font-light">تبدیل به سفارش</h1> <div className="flex flex-wrap items-center justify-between gap-4">
<div className="space-y-3">
{invoiceItemId && ( <BackButton to={backTo} />
<div className="mt-8 rounded-3xl bg-white p-6"> <div className="flex items-center gap-2 text-[#111827]">
<div className="font-light">اطلاعات آیتم پیش فاکتور</div> <DocumentText size={20} color="#111827" />
<h1 className="text-xl font-medium">تبدیل به سفارش</h1>
{isInvoiceItemLoading ? (
<div className="mt-6 flex min-h-[120px] items-center justify-center text-[#888888]">
در حال بارگذاری...
</div> </div>
) : invoiceItem ? ( {invoiceItemId && invoiceItem?.product?.title && (
<div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"> <p className="text-sm text-[#6B7280]">
<div> ایجاد سفارش از آیتم «{invoiceItem.product.title}»
<div className="mb-1.5 text-[13px] text-[#888888]">پیش فاکتور</div> </p>
<Link )}
to={Paths.perfomaInvoice.detail + invoiceItem.invoice.id} </div>
className="text-[15px] text-primary hover:underline"
<Button
type="button"
className="w-fit px-5"
onClick={() => formik.handleSubmit()}
isLoading={isSubmitting}
> >
#{invoiceItem.invoice.invoiceNumber} <div className="flex items-center gap-2">
</Link> <TickSquare size={20} color="black" />
<span>ثبت سفارش</span>
</div> </div>
<div> </Button>
<div className="mb-1.5 text-[13px] text-[#888888]">مشتری</div>
<div className="text-[15px]">{customerName}</div>
</div> </div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">محصول</div>
<div className="text-[15px]">{invoiceItem.product?.title ?? '-'}</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">تعداد</div>
<div className="text-[15px]">{invoiceItem.quantity ?? '-'}</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">مبلغ واحد</div>
<div className="text-[15px]">
{invoiceItem.unitPrice != null
? formatAmount(invoiceItem.unitPrice)
: '-'}
</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">تخفیف (مبلغ)</div>
<div className="text-[15px]">{discounts?.value ?? '-'}</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">تخفیف (درصد)</div>
<div className="text-[15px]">{discounts?.percent ?? '-'}</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">مبلغ کل</div>
<div className="text-[15px] font-semibold">
{formatAmount(invoiceItem.total ?? 0)}
</div>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">تاریخ تایید</div>
<div className="text-[15px]">
{invoiceItem.confirmedAt
? moment(invoiceItem.confirmedAt).format('jYYYY/jMM/jDD HH:mm')
: '-'}
</div>
</div>
{invoiceItem.description && (
<div className="sm:col-span-2 lg:col-span-3">
<div className="mb-1.5 text-[13px] text-[#888888]">توضیحات</div>
<div className="whitespace-pre-wrap text-[15px]">
{invoiceItem.description}
</div>
</div>
)}
</div>
) : (
<div className="mt-6 text-[15px] text-red-500">
آیتم پیش فاکتور یافت نشد.
</div>
)}
</div>
)}
<div className="bg-white rounded-3xl p-6 mt-8"> <div
<form className={
onSubmit={formik.handleSubmit} invoiceItemId
className="space-y-6" ? 'grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_320px]'
: ''
}
> >
<div className="rounded-3xl border border-border/70 bg-white p-6 shadow-sm">
<div className="font-medium text-[#111827]">اطلاعات سفارش</div>
<p className="mt-1 text-[13px] text-[#6B7280]">
جزئیات مورد نیاز برای ثبت سفارش را تکمیل کنید.
</p>
<form onSubmit={formik.handleSubmit} className="mt-6 space-y-6">
{!invoiceItemId && ( {!invoiceItemId && (
<div className="rowTwoInput"> <div className="rowTwoInput">
<div className="w-full"> <div className="w-full">
@@ -211,7 +209,7 @@ const ConvertToOrders: FC = () => {
}} }}
/> />
{formik.touched.userId && formik.errors.userId ? ( {formik.touched.userId && formik.errors.userId ? (
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium"> <div className="mt-2 mr-2 text-right text-xs font-medium text-red-600">
{formik.errors.userId} {formik.errors.userId}
</div> </div>
) : null} ) : null}
@@ -236,19 +234,20 @@ const ConvertToOrders: FC = () => {
)} )}
<div className="rowTwoInput"> <div className="rowTwoInput">
<Select <Select
items={admins?.data?.map((item) => { items={
return { admins?.data?.map((item) => ({
label: item.firstName + ' ' + item.lastName, label: item.firstName + ' ' + item.lastName,
value: item.id value: item.id,
})) || []
} }
}) || []}
label="طراح" label="طراح"
placeholder="انتخاب طراح" placeholder="انتخاب طراح"
{...formik.getFieldProps('designerId')} {...formik.getFieldProps('designerId')}
error_text={ error_text={
formik.touched.designerId && formik.errors.designerId ? formik.errors.designerId : '' formik.touched.designerId && formik.errors.designerId
? formik.errors.designerId
: ''
} }
/> />
@@ -263,7 +262,9 @@ const ConvertToOrders: FC = () => {
placeholder="انتخاب نوع" placeholder="انتخاب نوع"
{...formik.getFieldProps('typeId')} {...formik.getFieldProps('typeId')}
error_text={ error_text={
formik.touched.typeId && formik.errors.typeId ? formik.errors.typeId : '' formik.touched.typeId && formik.errors.typeId
? formik.errors.typeId
: ''
} }
/> />
</div> </div>
@@ -284,12 +285,13 @@ const ConvertToOrders: FC = () => {
label="عنوان" label="عنوان"
{...formik.getFieldProps('title')} {...formik.getFieldProps('title')}
error_text={ error_text={
formik.touched.title && formik.errors.title ? formik.errors.title : '' formik.touched.title && formik.errors.title
? formik.errors.title
: ''
} }
/> />
</div> </div>
<div>
<Textarea <Textarea
label="توضیحات" label="توضیحات"
placeholder="توضیحات را وارد کنید..." placeholder="توضیحات را وارد کنید..."
@@ -300,14 +302,19 @@ const ConvertToOrders: FC = () => {
: '' : ''
} }
/> />
</div>
{invoiceItemId && ( {invoiceItemId && (
<div> <div
className={`rounded-2xl border border-border/70 bg-[#FBFCFF] p-4 ${
!hasLinkedRequest && !isInvoiceItemLoading
? 'opacity-60'
: ''
}`}
>
<div <div
className={`flex items-center gap-2 ${ className={`flex items-center gap-2 ${
!hasLinkedRequest && !isInvoiceItemLoading !hasLinkedRequest && !isInvoiceItemLoading
? 'pointer-events-none opacity-50' ? 'pointer-events-none'
: '' : ''
}`} }`}
> >
@@ -320,7 +327,7 @@ const ConvertToOrders: FC = () => {
/> />
<label <label
htmlFor="keepRequestChats" htmlFor="keepRequestChats"
className="cursor-pointer select-none text-[13px]" className="cursor-pointer select-none text-[13px] text-[#1F2937]"
> >
انتقال چتهای درخواست به سفارش انتقال چتهای درخواست به سفارش
</label> </label>
@@ -333,7 +340,7 @@ const ConvertToOrders: FC = () => {
</div> </div>
)} )}
<div> <div className="rounded-2xl border border-dashed border-border/70 bg-[#FBFCFF] p-4">
<UploadBox <UploadBox
label="پیوست‌ها" label="پیوست‌ها"
isMultiple isMultiple
@@ -341,13 +348,120 @@ const ConvertToOrders: FC = () => {
/> />
</div> </div>
<div className="flex justify-end"> <div className="flex justify-end xl:hidden">
<Button type="submit" isLoading={multiUpload.isPending || isPending}> <Button type="submit" className="w-fit px-5" isLoading={isSubmitting}>
ثبت <div className="flex items-center gap-2">
<TickSquare size={20} color="black" />
<span>ثبت سفارش</span>
</div>
</Button> </Button>
</div> </div>
</form> </form>
</div> </div>
{invoiceItemId && (
<aside className="h-fit space-y-4">
<div className="rounded-3xl border border-border/70 bg-[#F7F9FF] p-5 shadow-sm">
<div className="mb-4 flex items-center gap-2 text-[#111827]">
<ReceiptText size={18} color="#111827" />
<span className="font-medium">آیتم پیش فاکتور</span>
</div>
{isInvoiceItemLoading ? (
<div className="flex min-h-[140px] items-center justify-center text-[#888888]">
در حال بارگذاری...
</div>
) : invoiceItem ? (
<div className="space-y-3">
<InfoField
label="پیش فاکتور"
icon={<ReceiptText size={14} color="#7D8597" />}
value={
<Link
to={Paths.perfomaInvoice.detail + invoiceItem.invoice.id}
className="text-primary hover:underline"
>
#{invoiceItem.invoice.invoiceNumber}
</Link>
}
/>
<InfoField
label="مشتری"
value={customerName}
icon={<Profile2User size={14} color="#7D8597" />}
/>
<InfoField
label="محصول"
value={invoiceItem.product?.title ?? '-'}
icon={<Box1 size={14} color="#7D8597" />}
/>
<InfoField
label="تاریخ تایید"
icon={<Calendar size={14} color="#7D8597" />}
value={
invoiceItem.confirmedAt
? moment(invoiceItem.confirmedAt).format(
'jYYYY/jMM/jDD HH:mm',
)
: '-'
}
/>
{invoiceItem.description && (
<div className="rounded-2xl border border-border/70 bg-white p-4">
<div className="mb-2 text-[12px] text-[#7D8597]">
توضیحات
</div>
<div className="whitespace-pre-wrap text-[14px] text-[#1F2937]">
{invoiceItem.description}
</div>
</div>
)}
</div>
) : (
<div className="rounded-xl border border-red-100 bg-red-50 px-3 py-3 text-[13px] text-red-700">
آیتم پیش فاکتور یافت نشد.
</div>
)}
</div>
{invoiceItem && !isInvoiceItemLoading && (
<div className="rounded-3xl border border-border/70 bg-[#F7F9FF] p-5 shadow-sm">
<div className="mb-4 flex items-center gap-2 text-[#111827]">
<WalletMoney size={18} color="#111827" />
<span className="font-medium">خلاصه مالی</span>
</div>
<div className="space-y-2">
<SummaryRow
label="تعداد"
value={String(invoiceItem.quantity ?? '-')}
/>
<SummaryRow
label="مبلغ واحد"
value={
invoiceItem.unitPrice != null
? formatAmount(invoiceItem.unitPrice)
: '-'
}
/>
<SummaryRow
label="تخفیف (مبلغ)"
value={discounts?.value ?? '-'}
/>
<SummaryRow
label="تخفیف (درصد)"
value={discounts?.percent ?? '-'}
/>
<SummaryRow
label="مبلغ کل"
value={formatAmount(invoiceItem.total ?? 0)}
valueClassName="text-[#111827]"
/>
</div>
</div>
)}
</aside>
)}
</div>
</div> </div>
) )
} }