From a5f9a3ee002c2c09a69db1244501d26b4c48ac79 Mon Sep 17 00:00:00 2001 From: Andres Fernandez Date: Thu, 6 Apr 2023 21:32:15 -0300 Subject: [PATCH 1/2] REFACTOR all classCreate page --- src/components/DialogTicket/DialogTocket.tsx | 132 ------- src/components/DialogTicket/index.ts | 3 - src/constants/programingLanguages.ts | 22 +- .../class/ClassCreate/ClassCreate.hooks.ts | 120 ++++++ .../ClassCreate/ClassCreate.interface.ts | 66 ++++ src/pages/class/ClassCreate/ClassCreate.tsx | 344 +++--------------- .../ClassCreateV2/ClassCreate.hooks.ts | 81 ----- .../ClassCreate/ClassCreateV2/ClassCreate.tsx | 107 ------ .../class/ClassCreate/ClassCreateV2/index.tsx | 3 - .../ClassCreate/DialogTicket/ClassDetail.tsx | 47 +++ .../DialogTicket/DialogTicket.hooks.ts | 13 + .../ClassCreate/DialogTicket/DialogTicket.tsx | 71 ++++ .../ClassCreate/DialogTicket/InputAmount.tsx | 48 +++ .../ClassCreate/DialogTicket/TicketDetail.tsx | 13 + .../DialogTicket/TicketOptions.tsx | 15 + .../class/ClassCreate/DialogTicket/index.ts | 3 + src/pages/class/ClassCreate/index.tsx | 166 ++++++++- .../ClassCreate/inputs/DatetimeInput.tsx | 24 ++ .../ClassCreate/inputs/RangeHourSelector.tsx | 18 + .../inputs/SelectProgramingLanguage.tsx | 34 ++ .../ClassCreate/inputs/SelectStudent.tsx | 29 ++ 21 files changed, 718 insertions(+), 641 deletions(-) delete mode 100644 src/components/DialogTicket/DialogTocket.tsx delete mode 100644 src/components/DialogTicket/index.ts create mode 100644 src/pages/class/ClassCreate/ClassCreate.hooks.ts create mode 100644 src/pages/class/ClassCreate/ClassCreate.interface.ts delete mode 100644 src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.hooks.ts delete mode 100644 src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.tsx delete mode 100644 src/pages/class/ClassCreate/ClassCreateV2/index.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/ClassDetail.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/DialogTicket.hooks.ts create mode 100644 src/pages/class/ClassCreate/DialogTicket/DialogTicket.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/InputAmount.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/TicketDetail.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/TicketOptions.tsx create mode 100644 src/pages/class/ClassCreate/DialogTicket/index.ts create mode 100644 src/pages/class/ClassCreate/inputs/DatetimeInput.tsx create mode 100644 src/pages/class/ClassCreate/inputs/RangeHourSelector.tsx create mode 100644 src/pages/class/ClassCreate/inputs/SelectProgramingLanguage.tsx create mode 100644 src/pages/class/ClassCreate/inputs/SelectStudent.tsx diff --git a/src/components/DialogTicket/DialogTocket.tsx b/src/components/DialogTicket/DialogTocket.tsx deleted file mode 100644 index eac5040..0000000 --- a/src/components/DialogTicket/DialogTocket.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import Grid2 from '@mui/material/Unstable_Grid2'; -import moment from 'moment'; -import { IClassFirebaseEntity } from '@interfaces/FirebaseEntitys'; -import MoneyOffIcon from '@mui/icons-material/MoneyOff'; -import React, { ChangeEvent } from 'react'; -import { TransitionProps } from '@mui/material/transitions'; -import { - Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - Slide, - TextField, -} from '@mui/material'; - -const Transition = React.forwardRef(function Transition( - props: TransitionProps & { - children: React.ReactElement< - unknown, - string | React.JSXElementConstructor - >; - }, - ref: React.Ref -) { - return ; -}); - -export interface DialogTicketProps { - classState: IClassFirebaseEntity; - popUpVisible: boolean; - price: number; - handleClose: () => void; - handleChangePrice: ( - event: ChangeEvent - ) => void; - handleOnConfirm: () => void; - handleOnPriceOff: (DiscountPercentage: number) => void; -} - -export const DialogTicket = ({ - classState, - popUpVisible, - price, - handleClose, - handleChangePrice, - handleOnConfirm, - handleOnPriceOff, -}: DialogTicketProps) => { - return ( - - {'Ticket'} - - -

- Clases de programacion en {classState.programingLeanguage.name} -

-

Datos del alumno

-

- Nombre: - {classState.student.firstName} {classState.student.lastName} -

-

- Dni: - {classState.student.dni} -

-

Datos de la clase

-

tema de la clase: {classState.programingLeanguage.name}

-

- fecha de la clase:{' '} - {moment(classState.dateTime).format('yyyy-mm-DD')} -

-

hora de la clase: {moment(classState.dateTime).format('HH:mm')}

-

duracion de la clase: {classState.duration} horas

- -

valor de la clase: {price}

-

Total: ${price * classState.duration}

-
- - - - - - - - -
- - - - -
- ); -}; diff --git a/src/components/DialogTicket/index.ts b/src/components/DialogTicket/index.ts deleted file mode 100644 index 347a433..0000000 --- a/src/components/DialogTicket/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { DialogTicket } from './DialogTocket'; - -export default DialogTicket; diff --git a/src/constants/programingLanguages.ts b/src/constants/programingLanguages.ts index b66afc5..152cb3a 100644 --- a/src/constants/programingLanguages.ts +++ b/src/constants/programingLanguages.ts @@ -1,13 +1,13 @@ -import { IProgramingLeanguaje } from '@interfaces/Domain'; +import { IProgramingLeanguajeFirebaseEntity } from '@interfaces/FirebaseEntitys'; -export const programingLanguages: IProgramingLeanguaje[] = [ - { name: 'C#' }, - { name: 'C++' }, - { name: 'C' }, - { name: 'Php' }, - { name: 'Java' }, - { name: 'JavaScript' }, - { name: 'TypeScript' }, - { name: 'Python' }, - { name: 'Html' }, +export const programingLanguages: IProgramingLeanguajeFirebaseEntity[] = [ + { id: '1', name: 'C#' }, + { id: '2', name: 'C++' }, + { id: '3', name: 'C' }, + { id: '4', name: 'Php' }, + { id: '5', name: 'Java' }, + { id: '6', name: 'JavaScript' }, + { id: '7', name: 'TypeScript' }, + { id: '8', name: 'Python' }, + { id: '9', name: 'Html' }, ]; diff --git a/src/pages/class/ClassCreate/ClassCreate.hooks.ts b/src/pages/class/ClassCreate/ClassCreate.hooks.ts new file mode 100644 index 0000000..363abc5 --- /dev/null +++ b/src/pages/class/ClassCreate/ClassCreate.hooks.ts @@ -0,0 +1,120 @@ +import { + IClassFirebaseEntity, + IProgramingLeanguajeFirebaseEntity, + IStudentFirebaseEntity, + ITicketFirebaseEntity, +} from '@interfaces/FirebaseEntitys'; +import moment from 'moment/moment'; +import { useEffect, useState } from 'react'; +import { useAppSelector } from '@store/hooks/hook'; +import { selectStudents } from '@slyces/students.slice'; +import { programingLanguages } from '@constants/programingLanguages'; +import { FORM_FORMAT_DATE } from '@constants/date'; +import { CLASS_PRICE } from '@constants/price'; + +export const useClassCreateForm = ( + initDurationClass: number, + classPrice: number = CLASS_PRICE +) => { + const { students } = useAppSelector(selectStudents); + const [studentSelected, setStudentSelected] = + useState(); + const [programingLanguageSelected, setProgramingLanguageSelected] = + useState( + programingLanguages.filter( + language => language.name.toLowerCase() === 'javascript' + )[0] + ); + const [duration, setDuration] = useState(initDurationClass); + const [datetime, setDatetime] = useState( + moment().format(FORM_FORMAT_DATE) + ); + const [price, setPrice] = useState(classPrice); + const [ticket, setTicket] = useState(); + + useEffect(() => { + updateTicket(); + }, [price, studentSelected, programingLanguageSelected, duration, datetime]); + + const selectStudentById = (idUser: string) => { + const studentFind = students.find( + (student: IStudentFirebaseEntity) => student.id === idUser + ); + if (studentFind === undefined) throw new Error('Student not found'); + setStudentSelected(studentFind); + }; + + const selectProgramingLanguageById = (idProgramingLanguage: string) => { + const programingLanguageFind = programingLanguages.find( + (programingLanguage: IProgramingLeanguajeFirebaseEntity) => + programingLanguage.id === idProgramingLanguage + ); + if (programingLanguageFind === undefined) + throw new Error('Programing language not found'); + setProgramingLanguageSelected(programingLanguageFind); + console.log(programingLanguageSelected); + }; + + const selectDuration = (duration: number) => { + if (duration < 0) throw new Error('Duration is negative'); + setDuration(duration); + }; + + const selectDatetime = (datetime: string) => { + const date = moment(datetime); + if (!date.isValid()) throw new Error('Datetime is not valid'); + // if (date.isAfter(moment())) throw new Error('Datetime is after now'); + setDatetime(datetime); + }; + + const createClass = (): IClassFirebaseEntity => { + if (studentSelected == null) throw new Error('Seleccione un estudiante'); + if (programingLanguageSelected == null) + throw new Error('Programing language not found'); + + return { + dateTime: moment(datetime).format('YYYY-MM-DDTHH:mm'), + duration, + programingLeanguage: programingLanguageSelected, + student: studentSelected, + }; + }; + + const createTicket = (): ITicketFirebaseEntity | undefined => { + if (studentSelected === undefined) return undefined; + return { + amount: getAmount(), + date: moment().format('YYYY-MM-DDTHH:mm'), + isPaid: false, + class: createClass(), + }; + }; + + const updateTicket = (): void => { + setTicket(createTicket()); + }; + + const getAmount = (): number => { + if (studentSelected === undefined) return 0; + return price * duration; + }; + + const updatePrice = (newPrice: number) => { + if (newPrice < 0) throw new Error('El precio no puede ser negativo'); + setPrice(newPrice); + }; + + return { + updateTicket, + ticket, + selectStudentById, + selectProgramingLanguageById, + selectDatetime, + selectDuration, + programingLanguageSelected, + datetime, + getAmount, + price, + updatePrice, + }; +}; diff --git a/src/pages/class/ClassCreate/ClassCreate.interface.ts b/src/pages/class/ClassCreate/ClassCreate.interface.ts new file mode 100644 index 0000000..9459ee6 --- /dev/null +++ b/src/pages/class/ClassCreate/ClassCreate.interface.ts @@ -0,0 +1,66 @@ +import React, { ReactNode } from 'react'; +import { AutocompleteChangeReason, SelectChangeEvent } from '@mui/material'; +import { + IProgramingLeanguajeFirebaseEntity, + IStudentFirebaseEntity, +} from '@interfaces/FirebaseEntitys'; + +export type EventGeneric = (event: React.ChangeEvent) => void; +export type OnChangeSelectorStudent = ( + event: React.SyntheticEvent, + value: IStudentFirebaseEntity, + reason: AutocompleteChangeReason +) => void; + +export type OnChangeSelectorDuration = ( + event: Event, + newValue: number | number[] +) => void; + +export type OnChangeSelectorDatetime = EventGeneric; +export interface ClassCreateProps { + onSubmit: (e: React.FormEvent) => void; + + onChangeSelectorProgramingLanguage: ( + event: SelectChangeEvent, + child: ReactNode + ) => void; + onChangeSelectorStudent: OnChangeSelectorStudent; + programingLanguageSelected: IProgramingLeanguajeFirebaseEntity; + + onChangeSelectorDuration: OnChangeSelectorDuration; + + onChangeSelectorDatetime: OnChangeSelectorDatetime; + + maxDurationClass: number; + durationStepRange: number; + initDuration: number; + selectedDatetime: string; +} + +export interface SelectStudentProps { + onChangeSelector: ( + event: React.SyntheticEvent, + value: IStudentFirebaseEntity, + reason: string, + details?: string + ) => void; + students: IStudentFirebaseEntity[]; +} + +export interface SelectProgramingLanguageProps { + onChangeSelector: OnChangeSelectorStudent; + programingLanguageSelected: IProgramingLeanguajeFirebaseEntity; +} + +export interface SelectDurationProps { + onChange: OnChangeSelectorDuration; + rangeMax: number; + step: number; + initValue: number; +} + +export interface SelectorDatetimeProps { + onChange: OnChangeSelectorDatetime; + selectedDatetime: string; +} diff --git a/src/pages/class/ClassCreate/ClassCreate.tsx b/src/pages/class/ClassCreate/ClassCreate.tsx index e1b3908..9a76b60 100644 --- a/src/pages/class/ClassCreate/ClassCreate.tsx +++ b/src/pages/class/ClassCreate/ClassCreate.tsx @@ -1,293 +1,69 @@ -import { - Box, - FormControl, - InputLabel, - MenuItem, - Select, - SelectChangeEvent, - TextField, -} from '@mui/material'; -import Grid2 from '@mui/material/Unstable_Grid2/Grid2'; - -import moment from 'moment'; -import React, { ChangeEvent, useEffect, useState } from 'react'; +import Grid2 from '@mui/material/Unstable_Grid2/Grid2.js'; import FabSubmit from '@components/FabSubmit'; import FormLayout from '@components/layers/FormLayout'; -import RangeSelectorInput from '@components/RangeSelectorInput'; -import { programingLanguages } from '@constants/programingLanguages'; -import { IProgramingLeanguaje, IStudent } from '@interfaces/Domain'; -import { - IClassFirebaseEntity, - IStudentFirebaseEntity, - ITicketFirebaseEntity, -} from '@interfaces/FirebaseEntitys'; -import ClassService from '@services/FirebaseServices/entityServices/ClassService'; -import StudentService from '@services/FirebaseServices/entityServices/StudentService'; -import { FormControlCustom } from '@styled/Forms.styled'; -import { CLASS_PRICE } from '@constants/price'; -import DialogTicket from '@components/DialogTicket'; -import { useAppDispatch } from '@store/hooks/hook'; -import { addTicket } from '@slyces/ticket.slice'; -import { toast } from 'react-toastify'; -import { useNavigate } from 'react-router-dom'; -import { message, messageError } from '@components/Toast'; -import { sendEmail } from '@/intercept/emailSender.interceptor'; -import { DATE_FORMAT_DIA_MES_ANIO } from '@constants/date'; - -export const ClassCreate: React.FunctionComponent = () => { - const navigate = useNavigate(); - const dispatch = useAppDispatch(); - const classDtoInitState: IClassFirebaseEntity = { - dateTime: moment().format('YYYY-MM-DDTHH:mm'), - duration: 1, - programingLeanguage: { name: '' }, - student: { - dni: moment().format('YYYYMMDDHHmmss'), - firstName: '', - lastName: '', - email: '', - phone: '', - birthDate: '', - }, - }; - const [classState, setClassState] = useState(classDtoInitState); - const [students, setStudents] = useState([] as IStudent[]); - const [popUpVisible, setPopUpVisible] = useState(false); - const [price, setPrice] = useState(CLASS_PRICE); - - useEffect(() => { - const serviceStudent = new StudentService(); - serviceStudent - .getAll() - .then((studentFromDatabase: IStudent[]) => { - setStudents(studentFromDatabase); - }) - .catch(error => { - console.error(error); - }); - }, []); - - const openDoalogTicket = () => { - setPopUpVisible(true); - }; - const handleClose = () => { - setPopUpVisible(false); - }; - - const handleOnChangePrice = ( - event: ChangeEvent - ) => { - setPrice(Number(event.target.value)); - }; - - const handleOnPriceOff = (DiscountPercentage: number) => { - setPrice(price * (1 - DiscountPercentage / 100)); - }; - - const handleOnChangeProgramingLeanguaje = (event: SelectChangeEvent) => { - setClassState({ - ...classState, - programingLeanguage: { name: event.target.value }, - }); - }; +import { useAppSelector } from '@store/hooks/hook'; +import { selectStudents } from '@slyces/students.slice'; +import { FC, ReactElement } from 'react'; +import { ClassCreateProps } from '@pages/class/ClassCreate/ClassCreate.interface'; +import { SelectStudent } from '@pages/class/ClassCreate/inputs/SelectStudent'; +import { SelectProgramingLanguage } from '@pages/class/ClassCreate/inputs/SelectProgramingLanguage'; +import { RangeHourSelector } from '@pages/class/ClassCreate/inputs/RangeHourSelector'; +import { DatetimeInput } from '@pages/class/ClassCreate/inputs/DatetimeInput'; - const handleOnChangeStudent = (event: SelectChangeEvent) => { - const studentFind = students.find( - (student: IStudent) => student.dni === event.target.value - ); +const COLUM_SMALL = 12; +const COLUM_MEDIUM = 6; - setClassState({ - ...classState, - student: - studentFind !== undefined - ? studentFind - : { - dni: '', - firstName: '', - lastName: '', - email: '', - phone: '', - birthDate: '', - }, - }); - }; - - const handleOnChange = (event: React.ChangeEvent) => { - setClassState({ - ...classState, - [event.target.name]: event.target.value, - }); - console.table(classState); - }; - - const handleChangeRange = (event: Event, newValue: number | number[]) => { - setClassState({ - ...classState, - duration: newValue as number, - }); - }; - - const handleOnConfirm = () => { - const classService = new ClassService(); - const newTicket: ITicketFirebaseEntity = { - amount: price * classState.duration, - date: moment().format('YYYY-MM-DD'), - class: classState, - isPaid: false, - }; - const toastRef = toast.loading('Procesando datos...'); - Promise.allSettled([ - classService.create(classState), - dispatch(addTicket(newTicket)), - ]) - .then(() => { - toast.update(toastRef, { - render: 'Clase almacenada con exito ', - type: 'success', - isLoading: false, - autoClose: 1500, - }); - sendEmail({ - addressed: classState.student.email, - subject: 'Resumen de la clase', - messageInHtmlFormat: `

Resumen de la clase del ${moment( - classState.dateTime - ).format(DATE_FORMAT_DIA_MES_ANIO)}

-

Nombre: ${classState.student.firstName} ${classState.student.lastName}

-

Apellido: ${classState.student.lastName}

-

Fecha: ${classState.dateTime}

-

Duracion: ${classState.duration}

-

Lenguaje: ${classState.programingLeanguage.name}

-

Precio por hora: ${price}

-

Precio total: ${price * classState.duration}

`, - }) - .then(() => { - message('Email enviado'); - }) - .catch(() => { - messageError('Error al enviar el email'); - }); - sendEmail({ - addressed: 'andres.fernandezcaballero@davinci.edu.ar', - subject: 'Resumen de la clase', - messageInHtmlFormat: `

Resumen de la clase

-

Nombre: ${classState.student.firstName} ${classState.student.lastName}

-

Apellido: ${classState.student.lastName}

-

Fecha: ${classState.dateTime}

-

Duracion: ${classState.duration}

-

Lenguaje: ${classState.programingLeanguage.name}

-

Precio por hora: ${price}

-

Precio total: ${price * classState.duration}

`, - }) - .then(() => { - message('Email enviado'); - }) - .catch(error => { - messageError('Error al enviar el email'); - console.log(error); - }); - navigate('/'); - }) - .catch(() => { - toast.update(toastRef, { - render: 'Error al guardar la clase', - type: 'error', - isLoading: false, - autoClose: 1500, - }); - }); - }; - - const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - openDoalogTicket(); - }; +export const ClassCreate: FC = ({ + onSubmit, + onChangeSelectorStudent, + onChangeSelectorProgramingLanguage, + programingLanguageSelected, + onChangeSelectorDuration, + onChangeSelectorDatetime, + initDuration, + maxDurationClass, + durationStepRange, + selectedDatetime, +}: ClassCreateProps): ReactElement => { + const { students } = useAppSelector(selectStudents); return ( - <> - - -
- - - - - Alumno - - - - - - - - - Lenguaje de Programacion - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - + +
+ +
); }; diff --git a/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.hooks.ts b/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.hooks.ts deleted file mode 100644 index c009722..0000000 --- a/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.hooks.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { IClassFirebaseEntity } from '@interfaces/FirebaseEntitys'; -import moment from 'moment/moment'; -import React, { useState } from 'react'; -import { ClassCreateDtoHook } from '@pages/class/ClassCreate/ClassCreate.interfaces'; -import { SelectChangeEvent } from '@mui/material'; - -export const useClassCreateDto = (): ClassCreateDtoHook => { - const initState: IClassFirebaseEntity = { - dateTime: moment().format('YYYY-MM-DDTHH:mm'), - duration: 1, - programingLeanguage: { name: '' }, - student: { - dni: moment().format('YYYYMMDDHHmmss'), - firstName: '', - lastName: '', - email: '', - phone: '', - birthDate: '', - }, - }; - - const [classDto, setClassDto] = useState(initState); - - const handleOnChange = (event: React.ChangeEvent) => { - setClassDto({ - ...classDto, - [event.target.name]: event.target.value, - }); - }; - - const handleChangeRange = (event: Event, newValue: number | number[]) => { - setClassDto({ - ...classDto, - duration: newValue as number, - }); - }; - - const handleOnChangeProgramingLanguage = (event: SelectChangeEvent) => { - setClassDto({ - ...classDto, - programingLeanguage: { name: event.target.value }, - }); - }; - - const handleOnChangeStudent = (/* event: SelectChangeEvent */) => { - const studentFind = undefined; - /* students.find( - (student: IStudent) => student.dni === event.target.value - ); - */ - - setClassDto({ - ...classDto, - student: - // TODO: revisar esto es raro - studentFind !== undefined - ? studentFind - : { - dni: '', - firstName: '', - lastName: '', - email: '', - phone: '', - birthDate: '', - }, - }); - }; - - const resetClassDto = (): void => { - setClassDto(initState); - }; - - return { - classDto, - handleOnChange, - handleChangeRange, - handleOnChangeProgramingLanguage, - handleOnChangeStudent, - resetClassDto, - }; -}; diff --git a/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.tsx b/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.tsx deleted file mode 100644 index 6c863c2..0000000 --- a/src/pages/class/ClassCreate/ClassCreateV2/ClassCreate.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import Grid2 from '@mui/material/Unstable_Grid2/Grid2.js'; -import { - Box, - FormControl, - InputLabel, - MenuItem, - Select, - TextField, -} from '@mui/material'; -import { IStudentFirebaseEntity } from '@interfaces/FirebaseEntitys.js'; -import { programingLanguages } from '@constants/programingLanguages.js'; -import { IProgramingLeanguaje } from '@interfaces/Domain.js'; -import RangeSelectorInput from '@components/RangeSelectorInput/index.js'; -import { FormControlCustom } from '@styled/Forms.styled.js'; -import FabSubmit from '@components/FabSubmit/index.js'; -import FormLayout from '@components/layers/FormLayout/index.js'; -import { useAppSelector } from '@store/hooks/hook.js'; -import { selectStudents } from '@slyces/students.slice.js'; - -export const ClassCreate = () => { - const { students } = useAppSelector(selectStudents); - - return ( - -
{ - /* do something */ - }} - > - - - - - Alumno - - - - - - - - - Lenguaje de Programacion - - - - - - - { - /* do something */ - }} - rangeMax={5} - step={0.5} - value={1} - /> - - - - { - /* do something */ - }} - value={'2023-12-31T10:30'} - InputLabelProps={{ - shrink: true, - }} - /> - - - - -
-
- ); -}; diff --git a/src/pages/class/ClassCreate/ClassCreateV2/index.tsx b/src/pages/class/ClassCreate/ClassCreateV2/index.tsx deleted file mode 100644 index ced9580..0000000 --- a/src/pages/class/ClassCreate/ClassCreateV2/index.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { ClassCreate } from '@pages/class/ClassCreate/ClassCreateV2/ClassCreate'; - -export default ClassCreate; diff --git a/src/pages/class/ClassCreate/DialogTicket/ClassDetail.tsx b/src/pages/class/ClassCreate/DialogTicket/ClassDetail.tsx new file mode 100644 index 0000000..ebbc5ee --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/ClassDetail.tsx @@ -0,0 +1,47 @@ +import moment from 'moment/moment'; +import { IClassFirebaseEntity } from '@interfaces/FirebaseEntitys'; +export interface TicketDetailProps { + classItem: IClassFirebaseEntity; +} +export const ClassDetail = ({ classItem }: TicketDetailProps) => ( +
+
+

Clases de {classItem.programingLeanguage.name}

+
+
+

Alumno

+

+ Nombre: + {classItem.student.firstName} {classItem.student.lastName} +

+

+ Dni: + {classItem.student.dni} +

+
+
+

Detalle

+
+

+ Tema: + {classItem.programingLeanguage.name} +

+

+ Duracion: + {classItem.duration} horas +

+
+
+

+ Fecha: + {moment(classItem.dateTime).format('yyyy-mm-DD')} +

+

+ Hora de inicio: + {moment(classItem.dateTime).format('HH:mm')} +

+
+
+
+
+); diff --git a/src/pages/class/ClassCreate/DialogTicket/DialogTicket.hooks.ts b/src/pages/class/ClassCreate/DialogTicket/DialogTicket.hooks.ts new file mode 100644 index 0000000..aa11d7c --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/DialogTicket.hooks.ts @@ -0,0 +1,13 @@ +import { useState } from 'react'; + +export const useOpenCloseDialogTicket = () => { + const [isOpen, setIsOpen] = useState(false); + const closeDialogTicket = () => setIsOpen(false); + const openDialogTicket = () => setIsOpen(true); + + return { + closeDialogTicket, + openDialogTicket, + isOpen, + }; +}; diff --git a/src/pages/class/ClassCreate/DialogTicket/DialogTicket.tsx b/src/pages/class/ClassCreate/DialogTicket/DialogTicket.tsx new file mode 100644 index 0000000..476d20b --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/DialogTicket.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { ITicketFirebaseEntity } from '@interfaces/FirebaseEntitys'; +import { TransitionProps } from '@mui/material/transitions'; +import { TicketDetail } from '@pages/class/ClassCreate/DialogTicket/TicketDetail'; +import { TicketOptions } from '@pages/class/ClassCreate/DialogTicket/TicketOptions'; +import { + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Slide, +} from '@mui/material'; +import { InputAmount } from '@pages/class/ClassCreate/DialogTicket/InputAmount'; + +const Transition = React.forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement< + unknown, + string | React.JSXElementConstructor + >; + }, + ref: React.Ref +) { + return ; +}); + +export interface DialogTicketProps { + popUpVisible: boolean; + onClose: () => void; + onConfirm: () => void; + ticket: ITicketFirebaseEntity | undefined; + price: number; + + updatePrice: (newPrice: number) => void; +} + +export const DialogTicket = ({ + popUpVisible, + onClose, + onConfirm, + ticket, + price, + updatePrice, +}: DialogTicketProps) => { + const handleOnChange = (event: React.ChangeEvent) => { + updatePrice(Number(event.target.value)); + }; + return ( + + {'Resumen de la clase'} + {ticket !== undefined && ( + + + + + + + )} + + + + + ); +}; diff --git a/src/pages/class/ClassCreate/DialogTicket/InputAmount.tsx b/src/pages/class/ClassCreate/DialogTicket/InputAmount.tsx new file mode 100644 index 0000000..750d82d --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/InputAmount.tsx @@ -0,0 +1,48 @@ +import Grid2 from '@mui/material/Unstable_Grid2'; +import { Button, TextField } from '@mui/material'; +import MoneyOffIcon from '@mui/icons-material/MoneyOff'; +import { ChangeEvent } from 'react'; + +export interface InputAmountProps { + onChange: (event: ChangeEvent) => void; + pricePerHour: number; +} + +export const InputAmount = ({ pricePerHour, onChange }: InputAmountProps) => { + return ( + + + + + + + + + ); +}; diff --git a/src/pages/class/ClassCreate/DialogTicket/TicketDetail.tsx b/src/pages/class/ClassCreate/DialogTicket/TicketDetail.tsx new file mode 100644 index 0000000..ea54bc6 --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/TicketDetail.tsx @@ -0,0 +1,13 @@ +import { ClassDetail } from '@pages/class/ClassCreate/DialogTicket/ClassDetail'; +import { ITicketFirebaseEntity } from '@interfaces/FirebaseEntitys'; + +export interface TicketDetailProps { + ticketItem: ITicketFirebaseEntity; +} + +export const TicketDetail = ({ ticketItem }: TicketDetailProps) => ( + <> + +

Total ${ticketItem.amount}

+ +); diff --git a/src/pages/class/ClassCreate/DialogTicket/TicketOptions.tsx b/src/pages/class/ClassCreate/DialogTicket/TicketOptions.tsx new file mode 100644 index 0000000..d374ee9 --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/TicketOptions.tsx @@ -0,0 +1,15 @@ +import { Button } from '@mui/material'; + +export interface TicketOptionProps { + onClose: () => void; + onConfirm: () => void; +} + +export const TicketOptions = ({ onClose, onConfirm }: TicketOptionProps) => ( + <> + + + +); diff --git a/src/pages/class/ClassCreate/DialogTicket/index.ts b/src/pages/class/ClassCreate/DialogTicket/index.ts new file mode 100644 index 0000000..d4e3102 --- /dev/null +++ b/src/pages/class/ClassCreate/DialogTicket/index.ts @@ -0,0 +1,3 @@ +import { DialogTicket } from './DialogTicket'; + +export default DialogTicket; diff --git a/src/pages/class/ClassCreate/index.tsx b/src/pages/class/ClassCreate/index.tsx index 3616386..4214c02 100644 --- a/src/pages/class/ClassCreate/index.tsx +++ b/src/pages/class/ClassCreate/index.tsx @@ -1,22 +1,148 @@ import { ClassCreate } from '@pages/class/ClassCreate/ClassCreate'; +import React, { FC, ReactElement } from 'react'; +import { SelectChangeEvent } from '@mui/material'; +import { toast } from 'react-toastify'; +import { useClassCreateForm } from '@pages/class/ClassCreate/ClassCreate.hooks'; +import { + IStudentFirebaseEntity, + ITicketFirebaseEntity, +} from '@interfaces/FirebaseEntitys'; +import { OnChangeSelectorDatetime } from '@pages/class/ClassCreate/ClassCreate.interface'; +import { useOpenCloseDialogTicket } from '@pages/class/ClassCreate/DialogTicket/DialogTicket.hooks'; +import DialogTicket from '@pages/class/ClassCreate/DialogTicket'; +import { CLASS_PRICE } from '@constants/price'; +import { useAppDispatch } from '@store/hooks/hook'; +import { addTicket } from '@slyces/ticket.slice'; +import { URL } from '@constants/routes'; +import { useNavigate } from 'react-router-dom'; +import { sendEmail } from '@/intercept/emailSender.interceptor'; +import ReactDomServer from 'react-dom/server'; +import { TicketDetail } from '@pages/class/ClassCreate/DialogTicket/TicketDetail'; -// -// const ComponentCreateClass = () => { -// -// const { classDto, resetClassDto, handleOnChange, handleChangeRange} = useClassCreateDto(); -// const handleOnSubmit = (e: React.FormEvent): void => { -// e.preventDefault(); -// alert(classDto.programingLeanguage) -// resetClassDto(); -// } -// -// return ( -// -// ) -// } - -export default ClassCreate; +const INIT_DURATION_CLASS = 1; +const DURATION_CLASS_RANGE = 0.5; +const MAX_DURATION_CLASS = 5; + +const Component: FC = (): ReactElement => { + const { + selectProgramingLanguageById, + selectStudentById, + ticket, + selectDuration, + selectDatetime, + datetime, + updatePrice, + price, + programingLanguageSelected, + } = useClassCreateForm(INIT_DURATION_CLASS, CLASS_PRICE); + + const { openDialogTicket, isOpen, closeDialogTicket } = + useOpenCloseDialogTicket(); + + const handleSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + try { + if (ticket === undefined) { + toast.error('Seleccione un usuario'); + return; + } + openDialogTicket(); + } catch (event) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + toast.error((event.message as string) ?? 'Error al crear ticket'); + } + }; + const dispatch = useAppDispatch(); + const navigate = useNavigate(); + const handleOnConfirm = (): void => { + const toastRef = toast.loading('Procesando datos...'); + if (ticket === undefined) toast.error('Ocurrio un error interno'); + dispatch(addTicket(ticket as ITicketFirebaseEntity)) + .then(() => { + toast.update(toastRef, { + render: 'Clase almacenada con exito ', + type: 'success', + isLoading: false, + autoClose: 1500, + }); + closeDialogTicket(); + toast('Enviando Comprobante...', { + autoClose: 1500, + }); + if (ticket?.class?.student !== undefined) { + sendEmail({ + addressed: 'andres.fernandezcaballero@davinci.edu.ar', + subject: 'Clase programada', + messageInHtmlFormat: ReactDomServer.renderToString( + + ), + }) + .then() + .catch(() => { + toast.error('Ocurrio un error al enviar el email'); + }); + } + navigate(URL.HOME); + }) + .catch(err => { + toast.error('Ocurrio un error al crear ticket'); + console.error(err); + }); + }; + + const handleChangeSelectorDatetime: OnChangeSelectorDatetime = ( + event: React.ChangeEvent + ): void => { + selectDatetime(event.target.value); + }; + + const handleChangeSelectorProgramingLanguage = ( + event: SelectChangeEvent + ): void => { + selectProgramingLanguageById(event.target.value); + }; + + const handleChangeSelectorStudent = ( + event: React.SyntheticEvent, + value: IStudentFirebaseEntity + ): void => { + selectStudentById(value.id as string); + }; + + const handleChangeSelectorDuration = ( + event: Event, + newValue: number | number[] + ): void => { + selectDuration(newValue as number); + }; + + return ( + <> + + + + + ); +}; +export default Component; diff --git a/src/pages/class/ClassCreate/inputs/DatetimeInput.tsx b/src/pages/class/ClassCreate/inputs/DatetimeInput.tsx new file mode 100644 index 0000000..d480c0f --- /dev/null +++ b/src/pages/class/ClassCreate/inputs/DatetimeInput.tsx @@ -0,0 +1,24 @@ +import { TextField } from '@mui/material'; +import { FormControlCustom } from '@styled/Forms.styled'; +import { SelectorDatetimeProps } from '@pages/class/ClassCreate/ClassCreate.interface'; + +export const DatetimeInput = ({ + onChange, + selectedDatetime, +}: SelectorDatetimeProps) => { + return ( + + + + ); +}; diff --git a/src/pages/class/ClassCreate/inputs/RangeHourSelector.tsx b/src/pages/class/ClassCreate/inputs/RangeHourSelector.tsx new file mode 100644 index 0000000..791adac --- /dev/null +++ b/src/pages/class/ClassCreate/inputs/RangeHourSelector.tsx @@ -0,0 +1,18 @@ +import RangeSelectorInput from '@components/RangeSelectorInput'; +import { SelectDurationProps } from '@pages/class/ClassCreate/ClassCreate.interface'; + +export const RangeHourSelector = ({ + onChange, + rangeMax, + initValue, + step, +}: SelectDurationProps) => { + return ( + + ); +}; diff --git a/src/pages/class/ClassCreate/inputs/SelectProgramingLanguage.tsx b/src/pages/class/ClassCreate/inputs/SelectProgramingLanguage.tsx new file mode 100644 index 0000000..0cd6811 --- /dev/null +++ b/src/pages/class/ClassCreate/inputs/SelectProgramingLanguage.tsx @@ -0,0 +1,34 @@ +import { Box, FormControl, InputLabel, MenuItem, Select } from '@mui/material'; +import { programingLanguages } from '@constants/programingLanguages'; +import { SelectProgramingLanguageProps } from '@pages/class/ClassCreate/ClassCreate.interface'; +import { IProgramingLeanguajeFirebaseEntity } from '@interfaces/FirebaseEntitys'; + +export const SelectProgramingLanguage = ({ + onChangeSelector, + programingLanguageSelected, +}: SelectProgramingLanguageProps) => ( + + + + Lenguaje de Programacion + + + + +); diff --git a/src/pages/class/ClassCreate/inputs/SelectStudent.tsx b/src/pages/class/ClassCreate/inputs/SelectStudent.tsx new file mode 100644 index 0000000..fb9825e --- /dev/null +++ b/src/pages/class/ClassCreate/inputs/SelectStudent.tsx @@ -0,0 +1,29 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck +import { Autocomplete, Box, FormControl, TextField } from '@mui/material'; + +import { SelectStudentProps } from '@pages/class/ClassCreate/ClassCreate.interface'; +import { ReactElement } from 'react'; + +export const SelectStudent = ({ + students, + onChangeSelector, +}: SelectStudentProps): ReactElement => { + return ( + + + ({ + ...student, + id: student.id, + label: student.firstName + ' ' + student.lastName, + }))} + onChange={onChangeSelector} + renderInput={params => } + > + + + ); +}; From de44db343ee095e2294a463eaa7d7552de5c81ae Mon Sep 17 00:00:00 2001 From: Andres Fernandez Date: Thu, 6 Apr 2023 21:47:44 -0300 Subject: [PATCH 2/2] REFACTOR all classCreate page --- src/pages/class/ClassCreate/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/class/ClassCreate/index.tsx b/src/pages/class/ClassCreate/index.tsx index 4214c02..d5eba5f 100644 --- a/src/pages/class/ClassCreate/index.tsx +++ b/src/pages/class/ClassCreate/index.tsx @@ -64,15 +64,15 @@ const Component: FC = (): ReactElement => { render: 'Clase almacenada con exito ', type: 'success', isLoading: false, - autoClose: 1500, + autoClose: 2000, }); closeDialogTicket(); toast('Enviando Comprobante...', { - autoClose: 1500, + autoClose: 2000, }); if (ticket?.class?.student !== undefined) { sendEmail({ - addressed: 'andres.fernandezcaballero@davinci.edu.ar', + addressed: ticket.class.student.email, subject: 'Clase programada', messageInHtmlFormat: ReactDomServer.renderToString(