crud schedule
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef, type FC } from 'react';
|
import { useState, useRef, useEffect, type FC } from 'react';
|
||||||
import { clx } from '../helpers/utils';
|
import { clx, formatTimeToHHmm } from '../helpers/utils';
|
||||||
import { Clock } from 'iconsax-react';
|
import { Clock } from 'iconsax-react';
|
||||||
import Error from './Error';
|
import Error from './Error';
|
||||||
|
|
||||||
@@ -18,9 +18,8 @@ const TimePickerComponent: FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.defaultValue) {
|
if (props.defaultValue) {
|
||||||
// تبدیل فرمت HH.mm به HH:mm برای input type="time"
|
const timeValue = formatTimeToHHmm(props.defaultValue.replace('.', ':'));
|
||||||
const timeStr = props.defaultValue.replace('.', ':');
|
setInputValue(timeValue);
|
||||||
setInputValue(timeStr);
|
|
||||||
}
|
}
|
||||||
}, [props.defaultValue]);
|
}, [props.defaultValue]);
|
||||||
|
|
||||||
@@ -29,9 +28,8 @@ const TimePickerComponent: FC<Props> = (props: Props) => {
|
|||||||
setInputValue(timeValue);
|
setInputValue(timeValue);
|
||||||
|
|
||||||
if (timeValue) {
|
if (timeValue) {
|
||||||
// تبدیل فرمت HH:mm به HH.mm برای ارسال به سرور
|
// فرمت HH:mm را مستقیماً ارسال میکنیم
|
||||||
const formattedTime = timeValue.replace(':', '.');
|
props.onChange(timeValue);
|
||||||
props.onChange(formattedTime);
|
|
||||||
} else {
|
} else {
|
||||||
props.onChange('');
|
props.onChange('');
|
||||||
}
|
}
|
||||||
@@ -67,11 +65,7 @@ const TimePickerComponent: FC<Props> = (props: Props) => {
|
|||||||
type="time"
|
type="time"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
className="w-full h-full bg-transparent border-0 outline-none cursor-pointer text-right time-picker-input"
|
className="absolute opacity-0 w-full h-full cursor-pointer"
|
||||||
style={{
|
|
||||||
color: inputValue ? '#000' : 'transparent',
|
|
||||||
caretColor: 'transparent'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{!inputValue && (
|
{!inputValue && (
|
||||||
<span className="absolute right-4 text-description pointer-events-none">
|
<span className="absolute right-4 text-description pointer-events-none">
|
||||||
@@ -80,7 +74,7 @@ const TimePickerComponent: FC<Props> = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
{inputValue && (
|
{inputValue && (
|
||||||
<span className="absolute right-4 pointer-events-none text-black">
|
<span className="absolute right-4 pointer-events-none text-black">
|
||||||
{inputValue.replace(':', '.')}
|
{inputValue}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<Clock size={20} color="#8C90A3" className='absolute left-2 top-1/2 -translate-y-1/2 pointer-events-none z-10' />
|
<Clock size={20} color="#8C90A3" className='absolute left-2 top-1/2 -translate-y-1/2 pointer-events-none z-10' />
|
||||||
|
|||||||
@@ -9,3 +9,18 @@ import { twMerge } from "tailwind-merge";
|
|||||||
export const clx = (...classes: (string | boolean | undefined)[]): string => {
|
export const clx = (...classes: (string | boolean | undefined)[]): string => {
|
||||||
return twMerge(classes.filter(Boolean).join(" "));
|
return twMerge(classes.filter(Boolean).join(" "));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts time string to HH:mm format
|
||||||
|
* @param time - Time string in various formats (HH:mm:ss, HH:mm, etc.)
|
||||||
|
* @returns Time string in HH:mm format
|
||||||
|
*/
|
||||||
|
export const formatTimeToHHmm = (time: string): string => {
|
||||||
|
if (!time) return '';
|
||||||
|
// اگر زمان به فرمت HH:mm:ss باشد، فقط HH:mm را برمیگرداند
|
||||||
|
if (time.includes(':') && time.split(':').length === 3) {
|
||||||
|
return time.substring(0, 5);
|
||||||
|
}
|
||||||
|
// اگر به فرمت HH:mm باشد، همان را برمیگرداند
|
||||||
|
return time;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
import { type FC, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
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 { useGetSchedule, useUpdateSchedule } 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 PageLoading from '@/components/PageLoading';
|
||||||
|
import { formatTimeToHHmm } from '@/helpers/utils';
|
||||||
|
|
||||||
|
const UpdateSchedule: FC = () => {
|
||||||
|
const { id } = useParams<{ id: string }>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { data: scheduleData, isLoading } = useGetSchedule(id || '');
|
||||||
|
const { mutate: updateSchedule, isPending } = useUpdateSchedule();
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}),
|
||||||
|
enableReinitialize: true,
|
||||||
|
onSubmit: (values) => {
|
||||||
|
if (!id) return;
|
||||||
|
updateSchedule(
|
||||||
|
{ id, params: values },
|
||||||
|
{
|
||||||
|
onSuccess: () => {
|
||||||
|
toast.success('زمانبندی با موفقیت ویرایش شد');
|
||||||
|
navigate(Pages.schedule.list);
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(extractErrorMessage(error));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (scheduleData?.data) {
|
||||||
|
const schedule = scheduleData.data;
|
||||||
|
formik.setValues({
|
||||||
|
weekDay: schedule.weekDay,
|
||||||
|
openTime: formatTimeToHHmm(schedule.openTime),
|
||||||
|
closeTime: formatTimeToHHmm(schedule.closeTime),
|
||||||
|
isActive: schedule.isActive,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [scheduleData]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <PageLoading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 UpdateSchedule;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import * as api from "../service/ScheduleService";
|
import * as api from "../service/ScheduleService";
|
||||||
|
import type { CreateScheduleType } from "../types/Types";
|
||||||
|
|
||||||
export const useGetSchedules = (params?: Record<string, unknown>) => {
|
export const useGetSchedules = (params?: Record<string, unknown>) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -27,3 +28,22 @@ export const useCreateSchedule = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useUpdateSchedule = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ id, params }: { id: string; params: CreateScheduleType }) =>
|
||||||
|
api.updateSchedule(id, params),
|
||||||
|
onSuccess: (_, { id }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["schedules"] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["schedules", id] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetSchedule = (id: string) => {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["schedule", id],
|
||||||
|
queryFn: () => api.getSchedule(id),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import axios from "@/config/axios";
|
|||||||
import type {
|
import type {
|
||||||
CreateScheduleType,
|
CreateScheduleType,
|
||||||
GetSchedulesResponseType,
|
GetSchedulesResponseType,
|
||||||
|
GetScheduleResponseType,
|
||||||
} from "../types/Types";
|
} from "../types/Types";
|
||||||
|
|
||||||
export const getSchedules = async (
|
export const getSchedules = async (
|
||||||
@@ -22,3 +23,18 @@ export const createSchedule = async (params: CreateScheduleType) => {
|
|||||||
const { data } = await axios.post("/schedules", params);
|
const { data } = await axios.post("/schedules", params);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const updateSchedule = async (
|
||||||
|
id: string,
|
||||||
|
params: CreateScheduleType
|
||||||
|
) => {
|
||||||
|
const { data } = await axios.patch(`/schedules/${id}`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSchedule = async (
|
||||||
|
id: string
|
||||||
|
): Promise<GetScheduleResponseType> => {
|
||||||
|
const { data } = await axios.get<GetScheduleResponseType>(`/schedules/${id}`);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type Schedule = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
deletedAt: string | null;
|
deletedAt: string | null;
|
||||||
|
weekDay: number;
|
||||||
openTime: string;
|
openTime: string;
|
||||||
closeTime: string;
|
closeTime: string;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
@@ -13,6 +14,8 @@ export type Schedule = {
|
|||||||
|
|
||||||
export type GetSchedulesResponseType = IResponse<Schedule[]>;
|
export type GetSchedulesResponseType = IResponse<Schedule[]>;
|
||||||
|
|
||||||
|
export type GetScheduleResponseType = IResponse<Schedule>;
|
||||||
|
|
||||||
export type CreateScheduleType = {
|
export type CreateScheduleType = {
|
||||||
weekDay: number;
|
weekDay: number;
|
||||||
openTime: string;
|
openTime: string;
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import Login from '../pages/auth/Login'
|
|||||||
|
|
||||||
const Auth: FC = () => {
|
const Auth: FC = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>sa
|
<Routes>
|
||||||
<Route path={Pages.auth.login + '/:slug'} element={<Login />} />
|
<Route path={Pages.auth.login + '/:slug'} element={<Login />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import CreateSchedule from '@/pages/schedule/Create'
|
|||||||
import RolesList from '@/pages/roles/List'
|
import RolesList from '@/pages/roles/List'
|
||||||
import CreateRole from '@/pages/roles/Create'
|
import CreateRole from '@/pages/roles/Create'
|
||||||
import UpdateRole from '@/pages/roles/Update'
|
import UpdateRole from '@/pages/roles/Update'
|
||||||
|
import UpdateSchedule from '@/pages/schedule/Update'
|
||||||
const MainRouter: FC = () => {
|
const MainRouter: FC = () => {
|
||||||
|
|
||||||
const { hasSubMenu } = useSharedStore()
|
const { hasSubMenu } = useSharedStore()
|
||||||
@@ -52,6 +53,7 @@ const MainRouter: FC = () => {
|
|||||||
<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 />} />
|
<Route path={Pages.schedule.create} element={<CreateSchedule />} />
|
||||||
|
<Route path={Pages.schedule.update + ':id'} element={<UpdateSchedule />} />
|
||||||
<Route path={Pages.roles.list} element={<RolesList />} />
|
<Route path={Pages.roles.list} element={<RolesList />} />
|
||||||
<Route path={Pages.roles.add} element={<CreateRole />} />
|
<Route path={Pages.roles.add} element={<CreateRole />} />
|
||||||
<Route path={Pages.roles.update + ':id'} element={<UpdateRole />} />
|
<Route path={Pages.roles.update + ':id'} element={<UpdateRole />} />
|
||||||
|
|||||||
Reference in New Issue
Block a user