designer request
This commit is contained in:
@@ -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 },
|
||||
|
||||
@@ -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;
|
||||
@@ -5,4 +5,7 @@ export const Paths = {
|
||||
catalog: {
|
||||
list: "/catalogue",
|
||||
},
|
||||
designer: {
|
||||
request: "/designer/request",
|
||||
},
|
||||
};
|
||||
|
||||
+22
-1
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user