diff --git a/paths.json b/paths.json index 53f49d9..954e064 100644 --- a/paths.json +++ b/paths.json @@ -2,7 +2,6 @@ "compilerOptions": { "baseUrl": "src", "paths": { - "@/*": ["*"], "@shared/*": ["shared/*"], "@doctor/*": ["modules/doctor/*"], "@manager/*": ["modules/manager/*"], diff --git a/src/modules/doctor/components/Home/Calendar/index.tsx b/src/modules/doctor/components/Home/Calendar/index.tsx index b878aa2..610fa4c 100644 --- a/src/modules/doctor/components/Home/Calendar/index.tsx +++ b/src/modules/doctor/components/Home/Calendar/index.tsx @@ -1,7 +1,8 @@ -import { IAppointments } from '@/modules/doctor/pages/Dashboard/interfaces'; import React from 'react'; import { FiChevronLeft, FiChevronRight } from 'react-icons/fi'; +import { IAppointments } from '@doctor/pages/Dashboard/interfaces'; + import { Container, DayNumber, diff --git a/src/modules/doctor/components/Patients/PatientItem/index.tsx b/src/modules/doctor/components/Patients/PatientItem/index.tsx index 9d3864f..56b9a2c 100644 --- a/src/modules/doctor/components/Patients/PatientItem/index.tsx +++ b/src/modules/doctor/components/Patients/PatientItem/index.tsx @@ -1,4 +1,4 @@ -import { IPatient } from '@/modules/doctor/pages/Patients/interfaces'; +import { IPatient } from '@doctor/pages/Patients/interfaces'; import React from 'react'; import { FiMoreVertical, FiTrash, FiPenTool } from 'react-icons/fi'; diff --git a/src/modules/secretary/components/Schedule/CreateAppointmentModal/index.tsx b/src/modules/secretary/components/Schedule/CreateAppointmentModal/index.tsx index 24121ed..a769f77 100644 --- a/src/modules/secretary/components/Schedule/CreateAppointmentModal/index.tsx +++ b/src/modules/secretary/components/Schedule/CreateAppointmentModal/index.tsx @@ -1,41 +1,151 @@ -import React, { useState } from 'react'; - -import { Container, NameTextField } from './styles'; - -const CreateAppointmentModal: React.FC = () => { - const mockVal = (str: string, repeat: number = 1) => { - return { - value: str.repeat(repeat), - }; - }; - - const [value, setValue] = useState(''); - const [options, setOptions] = useState<{ value: string }[]>([]); - const onSearch = (searchText: string) => { - setOptions( - !searchText - ? [] - : [mockVal(searchText), mockVal(searchText, 2), mockVal(searchText, 3)], - ); - }; - const onSelect = (data: string) => { - console.log('onSelect', data); - }; - const onChange = (data: string) => { - setValue(data); - }; +import React, { useCallback, useState } from 'react'; +import { message } from 'antd'; +import { format } from 'date-fns'; + +import TextField from '@shared/components/TextField'; +import Button from '@shared/components/Button'; + +import { ButtonWrapper, Container, TextFieldWrapper } from './styles'; +import api from '@shared/services/api'; +import { ICreateAppointmentModal, IPatientData } from './interfaces'; + +const CreateAppointmentModal: React.FC = ({ + doctorId, + doctorName, +}) => { + const [hasError, setHasError] = useState(false); + const [patientDocument, setPatientDocument] = useState(''); + const [appointmentDate, setAppointmentDate] = useState(''); + const [appointmentHour, setAppointmentHour] = useState(''); + const [appointmentType, setAppointmentType] = useState(''); + const [loading, setLoading] = useState(false); + + const handleSubmit = useCallback( + async e => { + e.preventDefault(); + + if ( + patientDocument && + appointmentDate && + appointmentHour && + appointmentType + ) { + try { + const patientResponse = await api.get( + `patients/document/${patientDocument}`, + ); + + const patientData: IPatientData = patientResponse.data; + + if (patientData.id) { + const data = { + date: format(new Date(appointmentDate), 'yyyy-mm-dd'), + start_time: appointmentHour, + status: 1, + type: appointmentType, + doctor_id: doctorId, + patient_id: patientData.id, + clinic_id: patientData.clinic_id, + }; + + const response = await api.post('appointments', data); + + console.log(response); + } else { + setHasError(true); + setLoading(false); + return message.error( + 'Não foi possível encontrar esse paciente. Tente novamente!', + ); + } + } catch (err) { + setHasError(true); + setLoading(false); + if (err.response) { + return message.error(err.response.data.message); + } + return message.error( + 'Ocorreu um erro interno na aplicação. Tente novamente mais tarde!', + ); + } + } else { + setHasError(true); + setLoading(false); + return message.error('Preencha todos os campos para prosseguir!'); + } + }, + [patientDocument, appointmentDate, appointmentHour, appointmentType], + ); return ( - +
+ + { + setPatientDocument(e.target.value); + setHasError(false); + }} + /> + + + { + setAppointmentDate(e.target.value); + setHasError(false); + }} + /> + { + setAppointmentHour(e.target.value); + setHasError(false); + }} + /> + + + { + setAppointmentType(e.target.value); + setHasError(false); + }} + /> + + + + + +
); }; diff --git a/src/modules/secretary/components/Schedule/CreateAppointmentModal/interfaces.ts b/src/modules/secretary/components/Schedule/CreateAppointmentModal/interfaces.ts new file mode 100644 index 0000000..bf83f26 --- /dev/null +++ b/src/modules/secretary/components/Schedule/CreateAppointmentModal/interfaces.ts @@ -0,0 +1,31 @@ +export interface IPatientData { + id: string; + name: string; + email: string; + phone: string; + date_of_birth: string; + gender: string; + skin_color: string; + naturalness: string; + marital_status: string; + ssn: string; + degree_of_instuction: string; + profession: string; + health_insurance: string; + zip_code: string; + address: string; + complement: string; + address_number: number; + neighborhood: string; + city: string; + fu: string; + avatar: null; + clinic_id: string; + created_at: string; + updated_at: string; +} + +export interface ICreateAppointmentModal { + doctorId: string; + doctorName: string; +} diff --git a/src/modules/secretary/components/Schedule/CreateAppointmentModal/styles.ts b/src/modules/secretary/components/Schedule/CreateAppointmentModal/styles.ts index 69f8eb0..7393666 100644 --- a/src/modules/secretary/components/Schedule/CreateAppointmentModal/styles.ts +++ b/src/modules/secretary/components/Schedule/CreateAppointmentModal/styles.ts @@ -1,13 +1,89 @@ -import { AutoComplete } from 'antd'; -import styled from 'styled-components'; +import styled, { css } from 'styled-components'; + +interface ITextFieldWrapperProps { + row?: boolean; +} + +interface ITextFieldProps { + hasError: boolean; + label: string; +} + +export const TextFieldWrapper = styled.div` + width: 100%; + + ${props => + props.row && + css` + display: flex; + align-items: center; + justify-content: space-between; + + div { + width: 47.5%; + } + `} +`; export const Container = styled.div` width: 100%; - padding: 40px; + padding: 40px 80px; > .ant-select { width: 100% !important; } + + ${TextFieldWrapper + TextFieldWrapper} { + margin-top: 30px; + } +`; + +export const ButtonWrapper = styled.div` + width: 100%; + display: flex; + align-items: center; + justify-content: center; + + margin-top: 50px; `; -export const NameTextField = styled(AutoComplete)``; +export const TextField = styled.input` + width: 600px; + height: 60px; + + box-shadow: 0px 0px 20px #eceff929; + border: 1px solid #b5bcc7; + border-radius: 10px; + background: #f4f5fa; + + padding: 0 20px; + font-size: 18px; + font-weight: 600; + + transition: border-color 0.2s; + + ${props => + props.hasError && + css` + border: 1px solid #fa7070; + `} + + &::placeholder { + color: #b5bcc7; + font-weight: 400; + } + + &:hover { + border-color: #7081fa; + } + + @media (max-width: 640px) { + width: 100%; + } + + @media (max-width: 520px) { + height: 55px; + font-size: 16px; + padding: 0 16px; + } +`; diff --git a/src/modules/secretary/components/Schedule/DoctorDropdown/index.tsx b/src/modules/secretary/components/Schedule/DoctorDropdown/index.tsx new file mode 100644 index 0000000..df24609 --- /dev/null +++ b/src/modules/secretary/components/Schedule/DoctorDropdown/index.tsx @@ -0,0 +1,72 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { DownOutlined } from '@ant-design/icons'; +import { Dropdown, Menu, message } from 'antd'; + +import api from '@shared/services/api'; + +import { SelectDoctorButton } from './styles'; +import { IDoctorData, IDoctorDropdownProps } from './interfaces'; + +const DoctorDropdown: React.FC = ({ + setDoctorId, + setDoctorName, +}) => { + const [selectedDoctorName, setSelectedDoctorName] = useState('carregando...'); + const [doctorList, setDoctorList] = useState([]); + + const DropdownMenu = useCallback( + () => ( + + {doctorList.map(doctor => ( + { + setSelectedDoctorName(doctor.user.name); + setDoctorName(doctor.user.name); + setDoctorId(doctor.id); + }} + > + {doctor.user.name} + + ))} + + ), + [doctorList], + ); + + const getDoctorInfo = useCallback(async () => { + try { + const response = await api.get('users/doctors'); + + const doctorData: IDoctorData[] = response.data; + + setDoctorList(doctorData); + setDoctorId(doctorData[0].id); + setSelectedDoctorName(doctorData[0].user.name); + setDoctorName(doctorData[0].user.name); + } catch (err) { + if (err.response) { + return message.error(err.response.data.message); + } + + return message.error( + 'Ocorreu um erro interno na aplicação. Tente novamente mais tarde!', + ); + } + }, []); + + useEffect(() => { + getDoctorInfo(); + }, []); + + return ( + + + {selectedDoctorName} + + + + ); +}; + +export default DoctorDropdown; diff --git a/src/modules/secretary/components/Schedule/DoctorDropdown/interfaces.ts b/src/modules/secretary/components/Schedule/DoctorDropdown/interfaces.ts new file mode 100644 index 0000000..d962dc7 --- /dev/null +++ b/src/modules/secretary/components/Schedule/DoctorDropdown/interfaces.ts @@ -0,0 +1,12 @@ +export interface IDoctorData { + id: string; + speciality: string; + start_time: string; + end_time: string; + user: { name: string }; +} + +export interface IDoctorDropdownProps { + setDoctorId(id: string): void; + setDoctorName(name: string): void; +} diff --git a/src/modules/secretary/components/Schedule/DoctorDropdown/styles.ts b/src/modules/secretary/components/Schedule/DoctorDropdown/styles.ts new file mode 100644 index 0000000..48af0b1 --- /dev/null +++ b/src/modules/secretary/components/Schedule/DoctorDropdown/styles.ts @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +export const SelectDoctorButton = styled.button` + height: 40px; + width: 180px; + + cursor: pointer; + background-color: #7081fa; + color: #fff; + border-radius: 7px; + padding: 8px 16px; + + border: 0; + outline: none; + + display: flex; + align-items: center; + justify-content: space-between; +`; diff --git a/src/modules/secretary/components/Schedule/DropdownMenu/index.tsx b/src/modules/secretary/components/Schedule/DropdownMenu/index.tsx deleted file mode 100644 index ffbfa3b..0000000 --- a/src/modules/secretary/components/Schedule/DropdownMenu/index.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Menu } from 'antd'; -import React from 'react'; - -// import { Container } from './styles'; - -const DropdownMenu = () => { - return ( - - - - 1st menu item - - - - - 2nd menu item - - - - - 3rd menu item - - - - ); -}; - -export default DropdownMenu; diff --git a/src/modules/secretary/components/Schedule/DropdownMenu/styles.ts b/src/modules/secretary/components/Schedule/DropdownMenu/styles.ts deleted file mode 100644 index c338983..0000000 --- a/src/modules/secretary/components/Schedule/DropdownMenu/styles.ts +++ /dev/null @@ -1,3 +0,0 @@ -import styled from 'styled-components'; - -export const Container = styled.div``; diff --git a/src/modules/secretary/pages/Schedule/index.tsx b/src/modules/secretary/pages/Schedule/index.tsx index 955907a..3cf71e6 100644 --- a/src/modules/secretary/pages/Schedule/index.tsx +++ b/src/modules/secretary/pages/Schedule/index.tsx @@ -1,29 +1,25 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Calendar, Badge, Skeleton, Dropdown, Modal } from 'antd'; -import { DownOutlined, PlusOutlined } from '@ant-design/icons'; +import { Calendar, Badge, Modal, message } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; import { format } from 'date-fns'; import Navbar from '@shared/components/Navbar'; import api from '@shared/services/api'; -import Loading from '@shared/components/Loading'; -import DropdownMenu from '@secretary/components/Schedule/DropdownMenu'; +import DoctorDropdown from '@secretary/components/Schedule/DoctorDropdown'; import CreateAppointmentModal from '@secretary/components/Schedule/CreateAppointmentModal'; import { IAppointment, IEvent } from './interfaces'; -import { - Container, - Header, - SelectDoctorButton, - NewAppointmentButton, -} from './styles'; +import { Container, Header, NewAppointmentButton } from './styles'; const Schedule: React.FC = () => { const [appointments, setAppointments] = useState([]); const [year, setYear] = useState(format(new Date(), 'yyyy')); const [month, setMonth] = useState(format(new Date(), 'MM')); - const [loading, setLoading] = useState(true); - const [visible, setVisible] = useState(true); + const [doctorId, setDoctorId] = useState(''); + const [doctorName, setDoctorName] = useState(''); + + const [visible, setVisible] = useState(false); const dateCellRender = useCallback( (value: any) => { @@ -47,8 +43,8 @@ const Schedule: React.FC = () => { return (
    - {listData.map(item => ( -
  • + {listData.map((item, index) => ( +
  • ))} @@ -71,64 +67,59 @@ const Schedule: React.FC = () => { const getAppointments = useCallback(async () => { try { const response = await api.get( - `appointments/month?month=${month}&year=${year}&limit=5000`, + `appointments/month/doctor/${doctorId}?month=${month}&year=${year}&limit=5000`, ); setAppointments(response.data); - setLoading(false); } catch (err) { - setLoading(false); + if (err.response) { + return message.error(err.response.data.message); + } + + return message.error( + 'Ocorreu um erro interno na aplicação. Tente novamente mais tarde!', + ); } - }, [month, year]); + }, [month, year, doctorId]); useEffect(() => { - getAppointments(); - }, [month, year]); + if (doctorId) { + getAppointments(); + } + }, [month, year, doctorId]); return ( - {loading ? ( - <> - - - - - - ) : ( - -
    -

    Agenda

    -
    - setVisible(true)}> - Nova consulta - - - - - - Dr Vinnicius - - - -
    -
    - - - setVisible(false)} - width={1000} - footer={false} - > - - -
    - )} + +
    +

    Agenda

    +
    + setVisible(true)}> + Nova consulta + + + +
    +
    + + + setVisible(false)} + width={1000} + footer={false} + > + + +
    ); }; diff --git a/src/modules/secretary/pages/Schedule/styles.ts b/src/modules/secretary/pages/Schedule/styles.ts index 1785cbb..7bc0202 100644 --- a/src/modules/secretary/pages/Schedule/styles.ts +++ b/src/modules/secretary/pages/Schedule/styles.ts @@ -62,20 +62,3 @@ export const NewAppointmentButton = styled.button` justify-content: space-between; `; -export const SelectDoctorButton = styled.button` - height: 40px; - width: 180px; - - cursor: pointer; - background-color: #7081fa; - color: #fff; - border-radius: 7px; - padding: 8px 16px; - - border: 0; - outline: none; - - display: flex; - align-items: center; - justify-content: space-between; -`; diff --git a/src/shared/components/Button/index.tsx b/src/shared/components/Button/index.tsx new file mode 100644 index 0000000..5f236dc --- /dev/null +++ b/src/shared/components/Button/index.tsx @@ -0,0 +1,11 @@ +import React, { ButtonHTMLAttributes } from 'react'; + +import { Container } from './styles'; + +interface IButtonProps extends ButtonHTMLAttributes {} + +const Button: React.FC = ({ children, ...rest }) => { + return {children}; +}; + +export default Button; diff --git a/src/shared/components/Button/styles.ts b/src/shared/components/Button/styles.ts new file mode 100644 index 0000000..0a18e20 --- /dev/null +++ b/src/shared/components/Button/styles.ts @@ -0,0 +1,30 @@ +import { shade } from 'polished'; +import styled from 'styled-components'; + +export const Container = styled.button` + border: 0; + background-color: #7081fa; + color: #ffffff; + font-size: 18px; + font-weight: 600; + width: 350px; + height: 55px; + border-radius: 5px; + box-shadow: 0px 0px 20px #eceff929; + + transition: background-color 0.2s; + + &:hover { + background: ${shade(0.2, '#7081fa')}; + } + + @media (max-width: 520px) { + width: 275px; + height: 45px; + margin-top: 40px; + margin-bottom: 20px; + + font-size: 17px; + font-weight: 500; + } +`; diff --git a/src/shared/components/TextField/index.tsx b/src/shared/components/TextField/index.tsx new file mode 100644 index 0000000..71accc0 --- /dev/null +++ b/src/shared/components/TextField/index.tsx @@ -0,0 +1,29 @@ +import React, { InputHTMLAttributes } from 'react'; + +import { Container, Input, Label } from './styles'; + +interface ITextFieldProps extends InputHTMLAttributes { + containerStyle?: object; + hasError?: boolean; + label?: string; +} + +const TextFieldComponent: React.FC = ({ + hasError = false, + label, + required, + ...rest +}) => { + return ( + + {label && ( + + )} + + + ); +}; + +export default TextFieldComponent; diff --git a/src/shared/components/TextField/styles.ts b/src/shared/components/TextField/styles.ts new file mode 100644 index 0000000..9b7faf9 --- /dev/null +++ b/src/shared/components/TextField/styles.ts @@ -0,0 +1,66 @@ +import styled, { css } from 'styled-components'; + +interface IInputProps { + hasError?: boolean; +} + +export const Container = styled.div` + width: 100%; +`; + +export const Input = styled.input` + width: 100%; + height: 57px; + + box-shadow: 0px 0px 20px #eceff929; + border: 1px solid #b5bcc7; + border-radius: 10px; + background: #f4f5fa; + + padding: 0 20px; + font-size: 18px; + font-weight: 600; + + transition: border-color 0.2s; + + ${props => + props.hasError && + css` + border: 1px solid #fa7070; + `} + + &::placeholder { + color: #b5bcc7; + font-weight: 400; + } + + &:hover { + border-color: #7081fa; + } + + @media (max-width: 640px) { + width: 100%; + } + + @media (max-width: 520px) { + height: 55px; + font-size: 16px; + padding: 0 16px; + } +`; + +export const Label = styled.span` + display: block; + margin-bottom: 10px; + + font-size: 20px; + font-weight: 600; + + b { + color: #fa7070; + } + + @media (max-width: 520px) { + font-size: 15px; + } +`; diff --git a/src/shared/hooks/auth.tsx b/src/shared/hooks/auth.tsx index fdc36a9..25482e7 100644 --- a/src/shared/hooks/auth.tsx +++ b/src/shared/hooks/auth.tsx @@ -1,4 +1,5 @@ import React, { createContext, useCallback, useState, useContext } from 'react'; +import { useHistory } from 'react-router-dom'; import api from '../services/api'; interface IDoctor { @@ -44,6 +45,8 @@ interface IAuthContextData { const AuthContext = createContext({} as IAuthContextData); const AuthProvider: React.FC = ({ children }) => { + const history = useHistory(); + const [data, setData] = useState(() => { const token = localStorage.getItem('@iDoctor:token'); const user = localStorage.getItem('@iDoctor:user'); @@ -88,6 +91,8 @@ const AuthProvider: React.FC = ({ children }) => { localStorage.removeItem('@iDoctor:doctor'); setData({} as IAuthState); + + window.location.href = '/'; }, []); return ( diff --git a/src/shared/pages/SignIn/index.tsx b/src/shared/pages/SignIn/index.tsx index 38c481b..260063e 100644 --- a/src/shared/pages/SignIn/index.tsx +++ b/src/shared/pages/SignIn/index.tsx @@ -5,6 +5,7 @@ import { useAuth } from '@shared/hooks/auth'; import vifeLogo from '@shared/assets/images/vife-logo.svg'; import idoctorLogo from '@shared/assets/images/idoctor-logo.svg'; +import Button from '@shared/components/Button'; import { Container, Background, Content, TextField } from './styles'; import { message, Spin } from 'antd'; @@ -32,7 +33,7 @@ const Home: React.FC = () => { } catch (err) { setHasError(true); setLoading(false); - if (err.response.data) { + if (err.response) { return message.error(err.response.data.message); } @@ -114,9 +115,9 @@ const Home: React.FC = () => { }} /> - + Esqueci minha senha diff --git a/src/shared/pages/SignIn/styles.ts b/src/shared/pages/SignIn/styles.ts index 345a0e3..6b569a4 100644 --- a/src/shared/pages/SignIn/styles.ts +++ b/src/shared/pages/SignIn/styles.ts @@ -265,7 +265,7 @@ export const Background = styled.div` export const TextField = styled.input` width: 600px; - height: 65px; + height: 60px; box-shadow: 0px 0px 20px #eceff929; border: 1px solid #b5bcc7; @@ -273,7 +273,7 @@ export const TextField = styled.input` background: #f4f5fa; padding: 0 20px; - font-size: 20px; + font-size: 18px; font-weight: 600; transition: border-color 0.2s; diff --git a/src/shared/styles/global.ts b/src/shared/styles/global.ts index fb603c4..af1f901 100644 --- a/src/shared/styles/global.ts +++ b/src/shared/styles/global.ts @@ -48,4 +48,8 @@ export default createGlobalStyle` .ant-modal-close-x { color: #FA7070 } + + .ant-modal-body { + padding: 0 + } `;