designer request

This commit is contained in:
hamid zarghami
2026-05-11 11:34:52 +03:30
parent 687f243b80
commit 327b74d7c0
8 changed files with 428 additions and 145 deletions
+2
View File
@@ -8,6 +8,8 @@ import { I18nextProvider } from 'react-i18next'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import ToastContainer from './components/Toast'
import { getToken, setRefreshToken, setToken } from './config/func'
import "react-multi-date-picker/styles/layouts/mobile.css"
i18next.init({
interpolation: { escapeValue: false },
+116
View File
@@ -0,0 +1,116 @@
import { useState, useEffect, useRef, type FC } from 'react';
import DatePicker from 'react-multi-date-picker';
import persian from 'react-date-object/calendars/persian';
import persian_fa from 'react-date-object/locales/persian_fa';
import DateObject from 'react-date-object';
import { clx } from '../helpers/utils';
import { Calendar } from 'iconsax-react';
type Props = {
onChange: (date: string) => void;
defaulValue?: string;
error_text?: string;
placeholder: string;
reset?: boolean;
isDateTime?: boolean;
className?: string;
label?: string
};
const DatePickerComponent: FC<Props> = (props: Props) => {
const [value, setValue] = useState<DateObject | null>(null);
const isSettingDefaultValue = useRef(false);
const isInitialized = useRef(false);
useEffect(() => {
if (props.reset) {
setValue(null);
}
}, [props.reset]);
useEffect(() => {
// فقط وقتی کاربر تاریخ رو تغییر داده onChange رو صدا بزن
if (isSettingDefaultValue.current) {
isSettingDefaultValue.current = false;
return;
}
// اگر هنوز initialize نشده و value null هست، onChange رو صدا نزن
if (!isInitialized.current && !value) {
return;
}
isInitialized.current = true;
if (value) {
// تبدیل تاریخ شمسی به میلادی
const gregorianDate = value.toDate();
const year = gregorianDate.getFullYear();
const month = String(gregorianDate.getMonth() + 1).padStart(2, '0');
const day = String(gregorianDate.getDate()).padStart(2, '0');
const formattedDate = `${year}-${month}-${day}`;
props.onChange(formattedDate);
} else {
props.onChange('');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
useEffect(() => {
if (props.defaulValue) {
try {
const defaultDate = new DateObject({
date: props.defaulValue,
calendar: persian,
locale: persian_fa,
});
const currentValueStr = value ? `${value.year}-${value.month.number}-${value.day}` : null;
const defaultValueStr = `${defaultDate.year}-${defaultDate.month.number}-${defaultDate.day}`;
if (currentValueStr !== defaultValueStr) {
isSettingDefaultValue.current = true;
setValue(defaultDate);
}
} catch (error) {
console.error('Error parsing date:', error);
}
} else if (!props.defaulValue && value) {
isSettingDefaultValue.current = true;
setValue(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [props.defaulValue]);
return (
<div className="w-full">
{props.label &&
<div className='text-sm'>
{props.label}
</div>}
<div className={clx(
'relative ',
props.label && 'mt-1.5'
)}>
<DatePicker
placeholder={props.placeholder}
value={value}
onChange={(date) => setValue(date as DateObject)}
calendar={persian}
locale={persian_fa}
calendarPosition="bottom-right"
className={`rmdp-mobile ${props.className}`}
/>
{props.error_text && props.error_text !== '' && (
<div className="text-xs text-right text-red-600 mt-2 mr-2 font-medium">
{props.error_text}
</div>
)}
<Calendar size={18} color='black' className='absolute top-0 bottom-0 my-auto left-2' />
</div>
</div>
);
};
export default DatePickerComponent;
+3
View File
@@ -5,4 +5,7 @@ export const Paths = {
catalog: {
list: "/catalogue",
},
designer: {
request: "/designer/request",
},
};
+22 -1
View File
@@ -1,5 +1,27 @@
@import "tailwindcss";
.rmdp-input {
min-height: 40px;
background-color: white;
border-radius: 12px !important;
border: 1px solid #d0d0d0 !important;
font-size: 12px !important;
width: 100% !important;
margin: 0px;
padding-right: 16px !important;
transition:
background-color 0.3s ease,
border-color 0.3s ease;
}
.readOny .rmdp-input {
background-color: #f5f5f5 !important;
border: none !important;
}
.rmdp-container {
width: 100%;
}
@layer base {
:root {
--background: 0 0% 100%;
@@ -123,7 +145,6 @@ html {
border-radius: 0;
}
/* جلوگیری از نمایش محتوای صفحات غیرفعال هنگام هاور */
.flipbook-container .stf__item {
overflow: hidden !important;
+104
View File
@@ -0,0 +1,104 @@
import { useMemo, useState, type FC } from 'react'
import Button from '@/components/Button'
import DatePicker from '@/components/DatePicker'
import Input from '@/components/Input'
import Select from '@/components/Select'
import Textarea from '@/components/Textarea'
import UploadBoxDraggble from '@/components/UploadBoxDraggble'
const PRICE_PER_PAGE = 1000
const pageCountOptions = Array.from({ length: 20 }, (_, index) => {
const value = index + 1
return {
value: String(value),
label: String(value),
}
})
const DesignerRequest: FC = () => {
const [catalogTitle, setCatalogTitle] = useState('')
const [, setDeliveryDate] = useState('')
const [pageCount, setPageCount] = useState('1')
const [description, setDescription] = useState('')
const [, setAttachment] = useState<File[]>([])
const totalPrice = useMemo(() => {
return Number(pageCount || 0) * PRICE_PER_PAGE
}, [pageCount])
const handleAttachmentChange = (files: File[]) => {
setAttachment(files)
}
return (
<div className='mt-4 w-full'>
<h1 className=''>درخواست طراحی کاتالوگ</h1>
<div className='rounded-[32px] bg-white p-4 md:p-6 lg:p-8 mt-6'>
<div className='mt-6 space-y-5'>
<Input
label='عنوان کاتالوگ'
value={catalogTitle}
onChange={(event) => setCatalogTitle(event.target.value)}
/>
<div className='grid grid-cols-1 gap-4 lg:grid-cols-2'>
<DatePicker
label='زمان تحویل موردنظر'
placeholder='تاریخ را انتخاب کنید'
onChange={(date) => setDeliveryDate(date)}
/>
<Select
label='تعداد صفحات'
value={pageCount}
items={pageCountOptions}
onChange={(event) => setPageCount(event.target.value)}
/>
</div>
<Textarea
label='توضیحات'
value={description}
onChange={(event) => setDescription(event.target.value)}
className='min-h-[96px]'
/>
<div>
<div className='mb-2 text-sm'>فایل های ضمیمه</div>
<UploadBoxDraggble
label='فایل مورد نظر را آپلود کنید'
onChange={handleAttachmentChange}
isMultiple={false}
isFile
/>
</div>
<div className='flex'>
<div className='flex-1'></div>
<div className='flex-1 flex gap-4'>
<div className='w-[250px]'>
<Input
label='قیمت به ازای هر صفحه'
value={`${PRICE_PER_PAGE.toLocaleString('fa-IR')} تومان`}
readOnly
/>
</div>
<Input
label='مبلغ قابل پرداخت برای شما'
value={`${totalPrice.toLocaleString('fa-IR')} تومان`}
readOnly
/>
</div>
</div>
<div className='flex justify-end mt-5'>
<Button className='w-fit px-6'>ثبت درخواست</Button>
</div>
</div>
</div >
</div >
)
}
export default DesignerRequest
+2
View File
@@ -8,6 +8,7 @@ import Home from '@/pages/home/Home'
import Editor from '@/pages/editor/Editor'
import Viewer from '@/pages/viewer/Viewer'
import CatalogueList from '@/pages/catalogue/List'
import DesignerRequest from '@/pages/designer/Request'
const MainRouter = () => {
const { hasSubMenu } = useSharedStore()
@@ -35,6 +36,7 @@ const MainRouter = () => {
<Route path={Paths.editor + '/:id'} element={<Editor />} />
<Route path={Paths.viewer + '/:id'} element={<Viewer />} />
<Route path={Paths.catalog.list} element={<CatalogueList />} />
<Route path={Paths.designer.request} element={<DesignerRequest />} />
</Routes>
</div>
</div>