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
+311 -197
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 * as Yup from 'yup'
import moment from 'moment-jalaali'
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 Textarea from '@/components/Textarea'
import Select from '@/components/Select'
import UploadBox from '@/components/UploadBox'
import Button from '@/components/Button'
import BackButton from '@/components/BackButton'
import { useMultiUpload } from '@/pages/uploader/hooks/useUploader'
import type { ConvertToOrderItemType } from './types/Types'
import { useConverToOrder, useGetProducts } from './hooks/useOrderData'
@@ -24,6 +34,35 @@ import { Checkbox } from '@/components/ui/checkbox'
const formatAmount = (amount: number) =>
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 => ({
invoiceItemId: invoiceItemId ?? '',
productId: '',
@@ -46,11 +85,10 @@ const getValidationSchema = (invoiceItemId: string | null) =>
estimatedDays: Yup.number().required('تخمین روز اجباری است.').min(0, 'باید عدد مثبت باشد'),
title: Yup.string().required('عنوان اجباری است.'),
description: Yup.string(),
designerId: Yup.string().required('این فیلد اجباری است')
designerId: Yup.string().required('این فیلد اجباری است'),
})
const ConvertToOrders: FC = () => {
const navigate = useNavigate()
const [searchParams] = useSearchParams()
const invoiceItemId = searchParams.get('invoiceItemId')
@@ -99,7 +137,7 @@ const ConvertToOrders: FC = () => {
},
onError: (error) => {
toast.error(extractErrorMessage(error))
}
},
})
},
})
@@ -110,186 +148,150 @@ const ConvertToOrders: FC = () => {
: invoiceItem?.invoice?.user?.phone ?? '-'
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 (
<div className="mt-5">
<h1 className="text-lg font-light">تبدیل به سفارش</h1>
{invoiceItemId && (
<div className="mt-8 rounded-3xl bg-white p-6">
<div className="font-light">اطلاعات آیتم پیش فاکتور</div>
{isInvoiceItemLoading ? (
<div className="mt-6 flex min-h-[120px] items-center justify-center text-[#888888]">
در حال بارگذاری...
</div>
) : invoiceItem ? (
<div className="mt-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">پیش فاکتور</div>
<Link
to={Paths.perfomaInvoice.detail + invoiceItem.invoice.id}
className="text-[15px] text-primary hover:underline"
>
#{invoiceItem.invoice.invoiceNumber}
</Link>
</div>
<div>
<div className="mb-1.5 text-[13px] text-[#888888]">مشتری</div>
<div className="text-[15px]">{customerName}</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 className="mt-5 space-y-8">
<div className="flex flex-wrap items-center justify-between gap-4">
<div className="space-y-3">
<BackButton to={backTo} />
<div className="flex items-center gap-2 text-[#111827]">
<DocumentText size={20} color="#111827" />
<h1 className="text-xl font-medium">تبدیل به سفارش</h1>
</div>
{invoiceItemId && invoiceItem?.product?.title && (
<p className="text-sm text-[#6B7280]">
ایجاد سفارش از آیتم «{invoiceItem.product.title}»
</p>
)}
</div>
)}
<div className="bg-white rounded-3xl p-6 mt-8">
<form
onSubmit={formik.handleSubmit}
className="space-y-6"
<Button
type="button"
className="w-fit px-5"
onClick={() => formik.handleSubmit()}
isLoading={isSubmitting}
>
{!invoiceItemId && (
<div className="rowTwoInput">
<div className="w-full">
<UserSearch
label="کاربر"
placeholder="جستجوی کاربر (نام، موبایل، ...)"
value={formik.values.userId}
onChange={(id) => {
formik.setFieldValue('userId', id)
formik.setFieldTouched('userId', true)
}}
<div className="flex items-center gap-2">
<TickSquare size={20} color="black" />
<span>ثبت سفارش</span>
</div>
</Button>
</div>
<div
className={
invoiceItemId
? '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 && (
<div className="rowTwoInput">
<div className="w-full">
<UserSearch
label="کاربر"
placeholder="جستجوی کاربر (نام، موبایل، ...)"
value={formik.values.userId}
onChange={(id) => {
formik.setFieldValue('userId', id)
formik.setFieldTouched('userId', true)
}}
/>
{formik.touched.userId && formik.errors.userId ? (
<div className="mt-2 mr-2 text-right text-xs font-medium text-red-600">
{formik.errors.userId}
</div>
) : null}
</div>
<Select
items={
products?.data?.map((item) => ({
label: item.title,
value: String(item.id),
})) ?? []
}
label="محصول"
placeholder="انتخاب محصول"
{...formik.getFieldProps('productId')}
error_text={
formik.touched.productId && formik.errors.productId
? formik.errors.productId
: ''
}
/>
{formik.touched.userId && formik.errors.userId ? (
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium">
{formik.errors.userId}
</div>
) : null}
</div>
)}
<div className="rowTwoInput">
<Select
items={
products?.data?.map((item) => ({
admins?.data?.map((item) => ({
label: item.firstName + ' ' + item.lastName,
value: item.id,
})) || []
}
label="طراح"
placeholder="انتخاب طراح"
{...formik.getFieldProps('designerId')}
error_text={
formik.touched.designerId && formik.errors.designerId
? formik.errors.designerId
: ''
}
/>
<Select
items={
categories?.data?.map((item) => ({
label: item.title,
value: String(item.id),
})) ?? []
}
label="محصول"
placeholder="انتخاب محصول"
{...formik.getFieldProps('productId')}
label="نوع"
placeholder="انتخاب نوع"
{...formik.getFieldProps('typeId')}
error_text={
formik.touched.productId && formik.errors.productId
? formik.errors.productId
formik.touched.typeId && formik.errors.typeId
? formik.errors.typeId
: ''
}
/>
</div>
)}
<div className="rowTwoInput">
<Select
items={admins?.data?.map((item) => {
return {
label: item.firstName + ' ' + item.lastName,
value: item.id
<div className="rowTwoInput">
<Input
label="تخمین روز"
type="number"
{...formik.getFieldProps('estimatedDays')}
error_text={
formik.touched.estimatedDays && formik.errors.estimatedDays
? formik.errors.estimatedDays
: ''
}
}) || []}
label="طراح"
placeholder="انتخاب طراح"
{...formik.getFieldProps('designerId')}
error_text={
formik.touched.designerId && formik.errors.designerId ? formik.errors.designerId : ''
}
/>
/>
<Select
items={
categories?.data?.map((item) => ({
label: item.title,
value: String(item.id),
})) ?? []
}
label="نوع"
placeholder="انتخاب نوع"
{...formik.getFieldProps('typeId')}
error_text={
formik.touched.typeId && formik.errors.typeId ? formik.errors.typeId : ''
}
/>
</div>
<Input
label="عنوان"
{...formik.getFieldProps('title')}
error_text={
formik.touched.title && formik.errors.title
? formik.errors.title
: ''
}
/>
</div>
<div className="rowTwoInput">
<Input
label="تخمین روز"
type="number"
{...formik.getFieldProps('estimatedDays')}
error_text={
formik.touched.estimatedDays && formik.errors.estimatedDays
? formik.errors.estimatedDays
: ''
}
/>
<Input
label="عنوان"
{...formik.getFieldProps('title')}
error_text={
formik.touched.title && formik.errors.title ? formik.errors.title : ''
}
/>
</div>
<div>
<Textarea
label="توضیحات"
placeholder="توضیحات را وارد کنید..."
@@ -300,53 +302,165 @@ const ConvertToOrders: FC = () => {
: ''
}
/>
</div>
{invoiceItemId && (
<div>
{invoiceItemId && (
<div
className={`flex items-center gap-2 ${
className={`rounded-2xl border border-border/70 bg-[#FBFCFF] p-4 ${
!hasLinkedRequest && !isInvoiceItemLoading
? 'pointer-events-none opacity-50'
? 'opacity-60'
: ''
}`}
>
<Checkbox
id="keepRequestChats"
checked={formik.values.keepRequestChats}
onCheckedChange={(checked) =>
formik.setFieldValue('keepRequestChats', !!checked)
}
/>
<label
htmlFor="keepRequestChats"
className="cursor-pointer select-none text-[13px]"
<div
className={`flex items-center gap-2 ${
!hasLinkedRequest && !isInvoiceItemLoading
? 'pointer-events-none'
: ''
}`}
>
انتقال چتهای درخواست به سفارش
</label>
<Checkbox
id="keepRequestChats"
checked={formik.values.keepRequestChats}
onCheckedChange={(checked) =>
formik.setFieldValue('keepRequestChats', !!checked)
}
/>
<label
htmlFor="keepRequestChats"
className="cursor-pointer select-none text-[13px] text-[#1F2937]"
>
انتقال چتهای درخواست به سفارش
</label>
</div>
{!hasLinkedRequest && !isInvoiceItemLoading && (
<p className="mt-2 text-xs text-[#888888]">
این پیشفاکتور به درخواستی متصل نیست.
</p>
)}
</div>
{!hasLinkedRequest && !isInvoiceItemLoading && (
<p className="mt-2 text-xs text-[#888888]">
این پیشفاکتور به درخواستی متصل نیست.
</p>
)}
<div className="rounded-2xl border border-dashed border-border/70 bg-[#FBFCFF] p-4">
<UploadBox
label="پیوست‌ها"
isMultiple
onChange={setAttachmentFiles}
/>
</div>
<div className="flex justify-end xl:hidden">
<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>
</div>
</form>
</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>
)}
<div>
<UploadBox
label="پیوست‌ها"
isMultiple
onChange={setAttachmentFiles}
/>
</div>
<div className="flex justify-end">
<Button type="submit" isLoading={multiUpload.isPending || isPending}>
ثبت
</Button>
</div>
</form>
{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>
)