diff --git a/package-lock.json b/package-lock.json index 18a11ba..21ce128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,9 +25,11 @@ "dayjs": "^1.11.9", "history": "^5.3.0", "jest-docblock": "^29.4.3", + "notistack": "^3.0.1", "react-hook-form": "^7.43.9", "react-redux": "^8.0.7", - "react-router-dom": "^6.14.0" + "react-router-dom": "^6.14.0", + "uuid": "^9.0.0" }, "devDependencies": { "@types/react": "^18.0.28", @@ -55,6 +57,7 @@ "react-dom": "^18.2.0", "redux-mock-store": "^1.5.4", "tailwindcss": "^3.3.2", + "tailwindcss-animated": "^1.0.1", "vite": "^4.3.2" } }, @@ -477,6 +480,15 @@ "node": ">= 0.12" } }, + "node_modules/@cypress/request/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", @@ -4252,6 +4264,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6465,6 +6485,15 @@ "node": ">=8" } }, + "node_modules/mochawesome/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6530,6 +6559,35 @@ "node": ">=0.10.0" } }, + "node_modules/notistack": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-3.0.1.tgz", + "integrity": "sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==", + "dependencies": { + "clsx": "^1.1.0", + "goober": "^2.0.33" + }, + "engines": { + "node": ">=12.0.0", + "npm": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/notistack" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/notistack/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -8032,6 +8090,15 @@ "node": ">=14.0.0" } }, + "node_modules/tailwindcss-animated": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tailwindcss-animated/-/tailwindcss-animated-1.0.1.tgz", + "integrity": "sha512-u5wusj89ZwP8I+s8WZlaAd7aZTWBN/XEG6QgMKpkIKmAf3xP1A6WYf7oYIKmGaB10UAQaSqWopi/i1ozzZEs8Q==", + "dev": true, + "peerDependencies": { + "tailwindcss": ">=3.1.0" + } + }, "node_modules/tcomb": { "version": "3.2.29", "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", @@ -8367,10 +8434,9 @@ "dev": true }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 9cda6b0..902cfe9 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,11 @@ "dayjs": "^1.11.9", "history": "^5.3.0", "jest-docblock": "^29.4.3", + "notistack": "^3.0.1", "react-hook-form": "^7.43.9", "react-redux": "^8.0.7", - "react-router-dom": "^6.14.0" + "react-router-dom": "^6.14.0", + "uuid": "^9.0.0" }, "devDependencies": { "@types/react": "^18.0.28", @@ -60,6 +62,7 @@ "react-dom": "^18.2.0", "redux-mock-store": "^1.5.4", "tailwindcss": "^3.3.2", + "tailwindcss-animated": "^1.0.1", "vite": "^4.3.2" } } diff --git a/src/App.jsx b/src/App.jsx index 91f2f29..27dc2e3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,6 +11,8 @@ import AppRouter from "./Components/AppRouter"; import { Provider } from "react-redux"; import DialogStack from "./dialog/DialogStack"; import Auth from "./Components/Auth"; +import SnackbarStack from "./Components/snackbars/SnackbarStack"; +import { SnackbarProvider } from "notistack"; const theme = createTheme({ components: { @@ -60,9 +62,14 @@ function App() { - - - + + + + + + diff --git a/src/Components/snackbars/SnackbarStack.jsx b/src/Components/snackbars/SnackbarStack.jsx new file mode 100644 index 0000000..2318167 --- /dev/null +++ b/src/Components/snackbars/SnackbarStack.jsx @@ -0,0 +1,32 @@ +import { Icon, IconButton } from "@mui/material"; +import { useSnackbar } from "notistack"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { shiftSnackbar } from "../../slices/snackbarSlice"; + +const SnackbarStack = () => { + const snackbarStack = useSelector(({ snackbar }) => snackbar.stack); + const dispatch = useDispatch(); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); + + useEffect(() => { + const lastSnackbar = snackbarStack[snackbarStack.length - 1]; + if (lastSnackbar) { + enqueueSnackbar(lastSnackbar?.message, { + variant: lastSnackbar?.variant, + action: (snackbarId) => ( + closeSnackbar(snackbarId)}> + close + + ), + }); + setTimeout(() => { + dispatch(shiftSnackbar()); + }, 10000); + } + }, [snackbarStack.length]); + + return null; +}; + +export default SnackbarStack; diff --git a/src/Pages/AppointmentInfo.jsx b/src/Pages/AppointmentInfo.jsx index 2c40cfa..53b3fc6 100644 --- a/src/Pages/AppointmentInfo.jsx +++ b/src/Pages/AppointmentInfo.jsx @@ -21,6 +21,7 @@ import { createAppointment, updateAppointment, deleteAppointment } from "../serv import DtxTextField from "../Components/Form/DtxTextField"; import DtxSelect from "../Components/Form/DtxSelect"; import DtxCheckbox from "../Components/Form/DtxCheckbox"; +import { showSnackbar } from '../slices/snackbarSlice'; const AppointmentInfo = ({ ...props }) => { @@ -126,8 +127,24 @@ const AppointmentInfo = ({ ...props }) => { await createAppointment(doctorId, appointmentDataToSend) } dispatch(incrementDataRevision({ event: "appointmentRevision" })) + dispatch( + showSnackbar({ + message: isEditMode + ? "Cambios guardados exitosamente" + : "La cita ha sido registrada con éxito en el sistema", + variant: "success", + }) + ); dispatch(popDialog()) } catch (err) { + dispatch( + showSnackbar({ + message: `Ha ocurrido un error al momento de ${ + isEditMode ? "actualizar" : "crear" + } la cita`, + variant: "error", + }) + ); console.error(err) } finally { setLoading(false) diff --git a/src/Pages/PatientInfo.jsx b/src/Pages/PatientInfo.jsx index e935f83..d0ef5ef 100644 --- a/src/Pages/PatientInfo.jsx +++ b/src/Pages/PatientInfo.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { useState, memo } from "react"; import { useForm } from "react-hook-form"; @@ -32,6 +32,7 @@ import { createPatient, updatePatient } from "../services/patientServices"; import { useDispatch, useSelector } from "react-redux"; import { popDialog } from "../slices/dialogSlice"; import { incrementDataRevision } from "../slices/revisionSlice"; +import { showSnackbar } from "../slices/snackbarSlice"; function TabPanel(props) { const { children, value, index, ...other } = props; @@ -99,8 +100,24 @@ const PatientInfo = ({ onProgress, ...props }) => { } dispatch(incrementDataRevision({ event: "patientRevision" })); + dispatch( + showSnackbar({ + message: isEditMode + ? "Cambios guardados exitosamente" + : "El paciente ha sido registrado con éxito en el sistema", + variant: "success", + }) + ); dispatch(popDialog()); } catch (err) { + dispatch( + showSnackbar({ + message: `Ha ocurrido un error al momento de ${ + isEditMode ? "actualizar" : "crear" + } el paciente`, + variant: "error", + }) + ); console.error(err); } finally { setLoading(false); diff --git a/src/reducer/store.js b/src/reducer/store.js index e11922e..ea58f12 100644 --- a/src/reducer/store.js +++ b/src/reducer/store.js @@ -4,13 +4,16 @@ import { dialogReducer } from "../slices/dialogSlice"; import { loginReducer } from "../slices/loginSlice"; import { userReducer } from "../slices/userSlice"; import { revisionReducer } from "../slices/revisionSlice"; +import { snackbarReducer } from "../slices/snackbarSlice"; + const store = configureStore({ reducer: { dialog: dialogReducer, login: loginReducer, user: userReducer, - revision: revisionReducer + revision: revisionReducer, + snackbar: snackbarReducer }, }); diff --git a/src/slices/snackbarSlice.js b/src/slices/snackbarSlice.js new file mode 100644 index 0000000..b356e30 --- /dev/null +++ b/src/slices/snackbarSlice.js @@ -0,0 +1,22 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { v4 as uuid } from "uuid"; + +const snackbarSlice = createSlice({ + name: "snackbar", + initialState: { + stack: [], + }, + reducers: { + showSnackbar: (state, action) => { + const snackbarId = uuid(); + state.stack.push({ id: snackbarId, ...action.payload }); + }, + shiftSnackbar: (state, action) => { + state.stack.shift(); + }, + }, +}); + +export const snackbarReducer = snackbarSlice.reducer; +export const { showSnackbar, shiftSnackbar } = snackbarSlice.actions; +export default snackbarSlice; diff --git a/tailwind.config.js b/tailwind.config.js index d2cbb00..c28d623 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -13,5 +13,5 @@ export default { }, }, }, - plugins: [], + plugins: [import('tailwindcss-animated')], };