From bf9424362c355bf56c050498eca183020607529d Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Tue, 18 Nov 2025 09:55:04 +0330 Subject: [PATCH] crud schedule --- src/components/TimePicker.tsx | 22 ++-- src/helpers/utils.ts | 15 +++ src/pages/schedule/Update.tsx | 101 ++++++++++++++++++ src/pages/schedule/hooks/useScheduleData.ts | 20 ++++ src/pages/schedule/service/ScheduleService.ts | 16 +++ src/pages/schedule/types/Types.ts | 3 + src/router/Auth.tsx | 2 +- src/router/Main.tsx | 2 + 8 files changed, 166 insertions(+), 15 deletions(-) create mode 100644 src/pages/schedule/Update.tsx diff --git a/src/components/TimePicker.tsx b/src/components/TimePicker.tsx index 5ecce44..dd0b8b3 100644 --- a/src/components/TimePicker.tsx +++ b/src/components/TimePicker.tsx @@ -1,5 +1,5 @@ -import { useState, useEffect, useRef, type FC } from 'react'; -import { clx } from '../helpers/utils'; +import { useState, useRef, useEffect, type FC } from 'react'; +import { clx, formatTimeToHHmm } from '../helpers/utils'; import { Clock } from 'iconsax-react'; import Error from './Error'; @@ -18,9 +18,8 @@ const TimePickerComponent: FC = (props: Props) => { useEffect(() => { if (props.defaultValue) { - // تبدیل فرمت HH.mm به HH:mm برای input type="time" - const timeStr = props.defaultValue.replace('.', ':'); - setInputValue(timeStr); + const timeValue = formatTimeToHHmm(props.defaultValue.replace('.', ':')); + setInputValue(timeValue); } }, [props.defaultValue]); @@ -29,9 +28,8 @@ const TimePickerComponent: FC = (props: Props) => { setInputValue(timeValue); if (timeValue) { - // تبدیل فرمت HH:mm به HH.mm برای ارسال به سرور - const formattedTime = timeValue.replace(':', '.'); - props.onChange(formattedTime); + // فرمت HH:mm را مستقیماً ارسال می‌کنیم + props.onChange(timeValue); } else { props.onChange(''); } @@ -67,11 +65,7 @@ const TimePickerComponent: FC = (props: Props) => { 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' - }} + className="absolute opacity-0 w-full h-full cursor-pointer" /> {!inputValue && ( @@ -80,7 +74,7 @@ const TimePickerComponent: FC = (props: Props) => { )} {inputValue && ( - {inputValue.replace(':', '.')} + {inputValue} )} diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index aa728fe..39f03ef 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -9,3 +9,18 @@ import { twMerge } from "tailwind-merge"; export const clx = (...classes: (string | boolean | undefined)[]): string => { 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; +}; diff --git a/src/pages/schedule/Update.tsx b/src/pages/schedule/Update.tsx new file mode 100644 index 0000000..8cd8e54 --- /dev/null +++ b/src/pages/schedule/Update.tsx @@ -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({ + 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 ; + } + + return ( +
+
+
+ ویرایش زمانبندی +
+
+ +
+
+ +
+ +
+
+ ); +}; + +export default UpdateSchedule; \ No newline at end of file diff --git a/src/pages/schedule/hooks/useScheduleData.ts b/src/pages/schedule/hooks/useScheduleData.ts index 8f4dc8a..1876deb 100644 --- a/src/pages/schedule/hooks/useScheduleData.ts +++ b/src/pages/schedule/hooks/useScheduleData.ts @@ -1,5 +1,6 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import * as api from "../service/ScheduleService"; +import type { CreateScheduleType } from "../types/Types"; export const useGetSchedules = (params?: Record) => { 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), + }); +}; diff --git a/src/pages/schedule/service/ScheduleService.ts b/src/pages/schedule/service/ScheduleService.ts index cb55b7a..09b7161 100644 --- a/src/pages/schedule/service/ScheduleService.ts +++ b/src/pages/schedule/service/ScheduleService.ts @@ -2,6 +2,7 @@ import axios from "@/config/axios"; import type { CreateScheduleType, GetSchedulesResponseType, + GetScheduleResponseType, } from "../types/Types"; export const getSchedules = async ( @@ -22,3 +23,18 @@ export const createSchedule = async (params: CreateScheduleType) => { const { data } = await axios.post("/schedules", params); 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 => { + const { data } = await axios.get(`/schedules/${id}`); + return data; +}; diff --git a/src/pages/schedule/types/Types.ts b/src/pages/schedule/types/Types.ts index bf0f989..8f50fea 100644 --- a/src/pages/schedule/types/Types.ts +++ b/src/pages/schedule/types/Types.ts @@ -5,6 +5,7 @@ export type Schedule = { createdAt: string; updatedAt: string; deletedAt: string | null; + weekDay: number; openTime: string; closeTime: string; isActive: boolean; @@ -13,6 +14,8 @@ export type Schedule = { export type GetSchedulesResponseType = IResponse; +export type GetScheduleResponseType = IResponse; + export type CreateScheduleType = { weekDay: number; openTime: string; diff --git a/src/router/Auth.tsx b/src/router/Auth.tsx index b85f96d..8476d1c 100644 --- a/src/router/Auth.tsx +++ b/src/router/Auth.tsx @@ -5,7 +5,7 @@ import Login from '../pages/auth/Login' const Auth: FC = () => { return ( - sa + } /> ) diff --git a/src/router/Main.tsx b/src/router/Main.tsx index 5d41323..38b265f 100644 --- a/src/router/Main.tsx +++ b/src/router/Main.tsx @@ -22,6 +22,7 @@ import CreateSchedule from '@/pages/schedule/Create' import RolesList from '@/pages/roles/List' import CreateRole from '@/pages/roles/Create' import UpdateRole from '@/pages/roles/Update' +import UpdateSchedule from '@/pages/schedule/Update' const MainRouter: FC = () => { const { hasSubMenu } = useSharedStore() @@ -52,6 +53,7 @@ const MainRouter: FC = () => { } /> } /> } /> + } /> } /> } /> } />