create schedule
This commit is contained in:
@@ -0,0 +1,97 @@
|
|||||||
|
import { useState, useEffect, useRef, type FC } from 'react';
|
||||||
|
import { clx } from '../helpers/utils';
|
||||||
|
import { Clock } from 'iconsax-react';
|
||||||
|
import Error from './Error';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onChange: (time: string) => void;
|
||||||
|
defaultValue?: string;
|
||||||
|
error_text?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
className?: string;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TimePickerComponent: FC<Props> = (props: Props) => {
|
||||||
|
const [inputValue, setInputValue] = useState<string>('');
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (props.defaultValue) {
|
||||||
|
// تبدیل فرمت HH.mm به HH:mm برای input type="time"
|
||||||
|
const timeStr = props.defaultValue.replace('.', ':');
|
||||||
|
setInputValue(timeStr);
|
||||||
|
}
|
||||||
|
}, [props.defaultValue]);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const timeValue = e.target.value; // فرمت: HH:mm
|
||||||
|
setInputValue(timeValue);
|
||||||
|
|
||||||
|
if (timeValue) {
|
||||||
|
// تبدیل فرمت HH:mm به HH.mm برای ارسال به سرور
|
||||||
|
const formattedTime = timeValue.replace(':', '.');
|
||||||
|
props.onChange(formattedTime);
|
||||||
|
} else {
|
||||||
|
props.onChange('');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWrapperClick = () => {
|
||||||
|
inputRef.current?.showPicker?.();
|
||||||
|
inputRef.current?.focus();
|
||||||
|
inputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
{props.label && (
|
||||||
|
<div className='text-sm'>
|
||||||
|
{props.label}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={clx(
|
||||||
|
'relative',
|
||||||
|
props.label && 'mt-1.5'
|
||||||
|
)}>
|
||||||
|
<div
|
||||||
|
onClick={handleWrapperClick}
|
||||||
|
className={clx(
|
||||||
|
'w-full bg-white h-10 text-black flex items-center px-4 pl-10 pr-4 text-xs rounded-xl border border-border cursor-pointer relative',
|
||||||
|
'hover:border-primary transition-colors',
|
||||||
|
props.className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="time"
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleChange}
|
||||||
|
className="w-full h-full bg-transparent border-0 outline-none cursor-pointer text-right time-picker-input"
|
||||||
|
style={{
|
||||||
|
color: inputValue ? '#000' : 'transparent',
|
||||||
|
caretColor: 'transparent'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!inputValue && (
|
||||||
|
<span className="absolute right-4 text-description pointer-events-none">
|
||||||
|
{props.placeholder || 'انتخاب زمان'}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{inputValue && (
|
||||||
|
<span className="absolute right-4 pointer-events-none text-black">
|
||||||
|
{inputValue.replace(':', '.')}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<Clock size={20} color="#8C90A3" className='absolute left-2 top-1/2 -translate-y-1/2 pointer-events-none z-10' />
|
||||||
|
</div>
|
||||||
|
{props.error_text && props.error_text !== '' && (
|
||||||
|
<Error errorText={props.error_text} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TimePickerComponent;
|
||||||
|
|
||||||
@@ -8,6 +8,7 @@ export const Pages = {
|
|||||||
schedule: {
|
schedule: {
|
||||||
list: "/schedule/list",
|
list: "/schedule/list",
|
||||||
update: "/schedule/update/",
|
update: "/schedule/update/",
|
||||||
|
create: "/schedule/create",
|
||||||
},
|
},
|
||||||
foods: {
|
foods: {
|
||||||
list: "/foods/list",
|
list: "/foods/list",
|
||||||
|
|||||||
@@ -79,3 +79,19 @@ tbody tr:nth-child(odd) {
|
|||||||
.rowTwoInput {
|
.rowTwoInput {
|
||||||
@apply flex flex-col sm:flex-row sm:gap-5 gap-5;
|
@apply flex flex-col sm:flex-row sm:gap-5 gap-5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.time-picker-input::-webkit-calendar-picker-indicator {
|
||||||
|
display: none !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-picker-input::-webkit-inner-spin-button,
|
||||||
|
.time-picker-input::-webkit-outer-spin-button {
|
||||||
|
display: none !important;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.time-picker-input {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { type FC } from 'react';
|
||||||
|
import { useFormik } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { TickCircle } from 'iconsax-react';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import ScheduleFormFields from './components/ScheduleFormFields';
|
||||||
|
import { useCreateSchedule } from './hooks/useScheduleData';
|
||||||
|
import type { CreateScheduleType } from './types/Types';
|
||||||
|
import { Pages } from '@/config/Pages';
|
||||||
|
import type { ErrorType } from '@/helpers/types';
|
||||||
|
import { extractErrorMessage } from '@/config/func';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const CreateSchedule: FC = () => {
|
||||||
|
const { mutate: createSchedule, isPending } = useCreateSchedule();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const formik = useFormik<CreateScheduleType>({
|
||||||
|
initialValues: {
|
||||||
|
weekDay: 1,
|
||||||
|
openTime: '',
|
||||||
|
closeTime: '',
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
validationSchema: Yup.object().shape({
|
||||||
|
weekDay: Yup.number()
|
||||||
|
.required('روز هفته الزامی است')
|
||||||
|
.min(0, 'روز هفته باید بین 1 تا 7 باشد')
|
||||||
|
.max(6, 'روز هفته باید بین 1 تا 7 باشد'),
|
||||||
|
openTime: Yup.string().required('زمان شروع الزامی است'),
|
||||||
|
closeTime: Yup.string().required('زمان پایان الزامی است'),
|
||||||
|
isActive: Yup.boolean(),
|
||||||
|
}),
|
||||||
|
onSubmit: (values) => {
|
||||||
|
createSchedule(values, {
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('زمانبندی با موفقیت ایجاد شد');
|
||||||
|
navigate(Pages.schedule.list);
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(extractErrorMessage(error));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-5'>
|
||||||
|
<div className='flex w-full justify-between items-center'>
|
||||||
|
<div className='text-lg font-light'>
|
||||||
|
زمانبندی جدید
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
className='px-5'
|
||||||
|
onClick={() => formik.handleSubmit()}
|
||||||
|
isLoading={isPending}
|
||||||
|
>
|
||||||
|
<div className='flex gap-2 items-center'>
|
||||||
|
<TickCircle className='size-5' color='white' />
|
||||||
|
<div>ثبت زمانبندی</div>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='mt-6'>
|
||||||
|
<ScheduleFormFields formik={formik} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CreateSchedule;
|
||||||
@@ -7,6 +7,8 @@ import { useGetSchedules, useDeleteSchedule } from './hooks/useScheduleData'
|
|||||||
import { useScheduleFilters } from './hooks/useScheduleFilters'
|
import { useScheduleFilters } from './hooks/useScheduleFilters'
|
||||||
import { getScheduleTableColumns } from './components/ScheduleTableColumns'
|
import { getScheduleTableColumns } from './components/ScheduleTableColumns'
|
||||||
import { useScheduleFiltersFields } from './components/ScheduleFiltersFields'
|
import { useScheduleFiltersFields } from './components/ScheduleFiltersFields'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { Pages } from '@/config/Pages'
|
||||||
|
|
||||||
const ScheduleList: FC = () => {
|
const ScheduleList: FC = () => {
|
||||||
const {
|
const {
|
||||||
@@ -34,12 +36,14 @@ const ScheduleList: FC = () => {
|
|||||||
<div className='mt-5'>
|
<div className='mt-5'>
|
||||||
<div className='flex justify-between items-center'>
|
<div className='flex justify-between items-center'>
|
||||||
<h1 className='text-lg font-light'>لیست زمانبندی</h1>
|
<h1 className='text-lg font-light'>لیست زمانبندی</h1>
|
||||||
<Button className='w-fit px-6'>
|
<Link to={Pages.schedule.create}>
|
||||||
<div className='flex gap-2 items-center'>
|
<Button className='w-fit px-6'>
|
||||||
<Add color='#fff' size={20} />
|
<div className='flex gap-2 items-center'>
|
||||||
<span>افزودن زمانبندی</span>
|
<Add color='#fff' size={20} />
|
||||||
</div>
|
<span>افزودن زمانبندی</span>
|
||||||
</Button>
|
</div>
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='mt-8'>
|
<div className='mt-8'>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { type FC } from 'react';
|
||||||
|
import type { FormikProps } from 'formik';
|
||||||
|
import Select from '@/components/Select';
|
||||||
|
import TimePicker from '@/components/TimePicker';
|
||||||
|
import Switch from '@/components/Switch';
|
||||||
|
import type { CreateScheduleType } from '../types/Types';
|
||||||
|
|
||||||
|
type ScheduleFormFieldsProps = {
|
||||||
|
formik: FormikProps<CreateScheduleType>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const weekDayOptions = [
|
||||||
|
{ value: '0', label: 'شنبه' },
|
||||||
|
{ value: '1', label: 'یکشنبه' },
|
||||||
|
{ value: '2', label: 'دوشنبه' },
|
||||||
|
{ value: '3', label: 'سهشنبه' },
|
||||||
|
{ value: '4', label: 'چهارشنبه' },
|
||||||
|
{ value: '5', label: 'پنجشنبه' },
|
||||||
|
{ value: '6', label: 'جمعه' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ScheduleFormFields: FC<ScheduleFormFieldsProps> = ({ formik }) => {
|
||||||
|
return (
|
||||||
|
<div className='bg-white py-8 xl:px-10 px-4 rounded-3xl space-y-5'>
|
||||||
|
<Select
|
||||||
|
label='روز هفته'
|
||||||
|
name='weekDay'
|
||||||
|
placeholder='انتخاب کنید'
|
||||||
|
items={weekDayOptions}
|
||||||
|
value={String(formik.values.weekDay)}
|
||||||
|
onChange={(e) => formik.setFieldValue('weekDay', Number(e.target.value))}
|
||||||
|
error_text={formik.touched.weekDay && formik.errors.weekDay ? String(formik.errors.weekDay) : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='grid grid-cols-2 gap-4'>
|
||||||
|
<TimePicker
|
||||||
|
label='زمان شروع'
|
||||||
|
placeholder='انتخاب زمان شروع'
|
||||||
|
defaultValue={formik.values.openTime}
|
||||||
|
onChange={(time) => formik.setFieldValue('openTime', time)}
|
||||||
|
error_text={formik.touched.openTime && formik.errors.openTime ? formik.errors.openTime : ''}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TimePicker
|
||||||
|
label='زمان پایان'
|
||||||
|
placeholder='انتخاب زمان پایان'
|
||||||
|
defaultValue={formik.values.closeTime}
|
||||||
|
onChange={(time) => formik.setFieldValue('closeTime', time)}
|
||||||
|
error_text={formik.touched.closeTime && formik.errors.closeTime ? formik.errors.closeTime : ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex items-center gap-3 pt-2'>
|
||||||
|
<Switch
|
||||||
|
active={formik.values.isActive}
|
||||||
|
onChange={(value) => formik.setFieldValue('isActive', value)}
|
||||||
|
label='فعال'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ScheduleFormFields;
|
||||||
|
|
||||||
@@ -17,3 +17,13 @@ export const useDeleteSchedule = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateSchedule = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: api.createSchedule,
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["schedules"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import axios from "@/config/axios";
|
import axios from "@/config/axios";
|
||||||
import type { GetSchedulesResponseType } from "../types/Types";
|
import type {
|
||||||
|
CreateScheduleType,
|
||||||
|
GetSchedulesResponseType,
|
||||||
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getSchedules = async (
|
export const getSchedules = async (
|
||||||
params?: Record<string, unknown>
|
params?: Record<string, unknown>
|
||||||
@@ -14,3 +17,8 @@ export const deleteSchedule = async (id: string) => {
|
|||||||
const { data } = await axios.delete(`/schedules/${id}`);
|
const { data } = await axios.delete(`/schedules/${id}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createSchedule = async (params: CreateScheduleType) => {
|
||||||
|
const { data } = await axios.post("/schedules", params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -12,3 +12,10 @@ export type Schedule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type GetSchedulesResponseType = IResponse<Schedule[]>;
|
export type GetSchedulesResponseType = IResponse<Schedule[]>;
|
||||||
|
|
||||||
|
export type CreateScheduleType = {
|
||||||
|
weekDay: number;
|
||||||
|
openTime: string;
|
||||||
|
closeTime: string;
|
||||||
|
isActive: boolean;
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import OrderDetails from '@/pages/orders/Details'
|
|||||||
import CommentsList from '@/pages/comments/List'
|
import CommentsList from '@/pages/comments/List'
|
||||||
import CommentsDetails from '@/pages/comments/Details'
|
import CommentsDetails from '@/pages/comments/Details'
|
||||||
import ScheduleList from '@/pages/schedule/List'
|
import ScheduleList from '@/pages/schedule/List'
|
||||||
|
import CreateSchedule from '@/pages/schedule/Create'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -47,6 +48,7 @@ const MainRouter: FC = () => {
|
|||||||
<Route path={Pages.comments.list} element={<CommentsList />} />
|
<Route path={Pages.comments.list} element={<CommentsList />} />
|
||||||
<Route path={Pages.comments.detail + ':id'} element={<CommentsDetails />} />
|
<Route path={Pages.comments.detail + ':id'} element={<CommentsDetails />} />
|
||||||
<Route path={Pages.schedule.list} element={<ScheduleList />} />
|
<Route path={Pages.schedule.list} element={<ScheduleList />} />
|
||||||
|
<Route path={Pages.schedule.create} element={<CreateSchedule />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user