diff --git a/.env.example b/.env.example index 05a32a5146..be0c04912e 100644 --- a/.env.example +++ b/.env.example @@ -11,4 +11,4 @@ DEBUG=TRUE # Front-End Variables VITE_BASENAME=/ -#VITE_BACKEND_URL= +VITE_BACKEND_URL=https://shiny-zebra-wrq6x99qwvjwhvv46-3001.app.github.dev/ diff --git a/Pipfile b/Pipfile index 4d377014ae..4085ff4201 100644 --- a/Pipfile +++ b/Pipfile @@ -12,7 +12,6 @@ flask-migrate = "*" flask-swagger = "*" psycopg2-binary = "*" python-dotenv = "*" -flask-cors = "*" gunicorn = "*" cloudinary = "*" flask-admin = "==2.0.0" @@ -20,6 +19,7 @@ typing-extensions = "*" flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" +flask-cors = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index d9e474e972..dc7d8b9015 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -42,11 +42,11 @@ }, "click": { "hashes": [ - "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", - "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" + "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", + "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d" ], "markers": "python_version >= '3.10'", - "version": "==8.3.0" + "version": "==8.3.2" }, "cloudinary": { "hashes": [ @@ -58,12 +58,12 @@ }, "flask": { "hashes": [ - "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", - "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c" + "sha256:0ef0e52b8a9cd932855379197dd8f94047b359ca0a78695144304cb45f87c9eb", + "sha256:f4bcbefc124291925f1a26446da31a5178f9483862233b23c0c96a20701f670c" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==3.1.2" + "version": "==3.1.3" }, "flask-admin": { "hashes": [ @@ -76,12 +76,12 @@ }, "flask-cors": { "hashes": [ - "sha256:c7b2cbfb1a31aa0d2e5341eea03a6805349f7a61647daee1a15c46bbe981494c", - "sha256:d81bcb31f07b0985be7f48406247e9243aced229b7747219160a0559edd678db" + "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", + "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a" ], "index": "pypi", "markers": "python_version >= '3.9' and python_version < '4.0'", - "version": "==6.0.1" + "version": "==6.0.2" }, "flask-jwt-extended": { "hashes": [ @@ -575,11 +575,11 @@ }, "werkzeug": { "hashes": [ - "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", - "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" + "sha256:63a77fb8892bf28ebc3178683445222aa500e48ebad5ec77b0ad80f8726b1f50", + "sha256:9bad61a4268dac112f1c5cd4630a56ede601b6ed420300677a869083d70a4c44" ], "markers": "python_version >= '3.9'", - "version": "==3.1.3" + "version": "==3.1.8" }, "wtforms": { "hashes": [ diff --git a/README.md b/README.md index 6b782b220d..3719c58dfd 100644 --- a/README.md +++ b/README.md @@ -79,3 +79,7 @@ This boilerplate it's 100% read to deploy with Render.com and Heroku in a matter This template was built as part of the 4Geeks Academy [Coding Bootcamp](https://4geeksacademy.com/us/coding-bootcamp) by [Alejandro Sanchez](https://twitter.com/alesanchezr) and many other contributors. Find out more about our [Full Stack Developer Course](https://4geeksacademy.com/us/coding-bootcamps/part-time-full-stack-developer), and [Data Science Bootcamp](https://4geeksacademy.com/us/coding-bootcamps/datascience-machine-learning). You can find other templates and resources like this at the [school github page](https://github.com/4geeksacademy/). + +# Elevator Speech: +*¿Sabías que la falta de comunicación es la mayor causa de estrés entre padres y niñeras?* He creado **Baby Zzzync**, una guía interactiva donde los padres configuran la rutina diaria y reciben notificaciones en vivo conforme la *babysitter* avanza en las tareas. Es una solución digital sencilla que profesionaliza el cuidado del bebé y da paz mental a los padres a través de datos en *tiempo real*. +**(Paz mental es el producto)** \ No newline at end of file diff --git a/dist/index.html b/dist/index.html index 1e8cb81dfe..3554b14f68 100644 --- a/dist/index.html +++ b/dist/index.html @@ -1,14 +1,18 @@ - - - - - - Hello Rigo with React + Flux + Context.js - - - - -
- - - + + + + + + + + + Baby Zzzync + + + + +
+ + + + \ No newline at end of file diff --git a/index.html b/index.html index 27a99f796e..fbe6016791 100644 --- a/index.html +++ b/index.html @@ -2,11 +2,11 @@ - - Hello Rigo + + Baby Zzzync
diff --git a/migrations/versions/070195b3b452_.py b/migrations/versions/070195b3b452_.py new file mode 100644 index 0000000000..3f18f8cb41 --- /dev/null +++ b/migrations/versions/070195b3b452_.py @@ -0,0 +1,45 @@ +"""empty message + +Revision ID: 070195b3b452 +Revises: 3b972ef6198c +Create Date: 2026-04-24 21:03:32.758063 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '070195b3b452' +down_revision = '3b972ef6198c' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('rutina', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('nombre', sa.String(length=120), nullable=False), + sa.Column('detalles', sa.String(length=250), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('actividad', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('text', sa.String(length=250), nullable=False), + sa.Column('time', sa.String(length=50), nullable=False), + sa.Column('category', sa.String(length=50), nullable=False), + sa.Column('rutina_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['rutina_id'], ['rutina.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('actividad') + op.drop_table('rutina') + # ### end Alembic commands ### diff --git a/migrations/versions/1d3b2e5ca6a5_.py b/migrations/versions/1d3b2e5ca6a5_.py new file mode 100644 index 0000000000..e8c86052ba --- /dev/null +++ b/migrations/versions/1d3b2e5ca6a5_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 1d3b2e5ca6a5 +Revises: 070195b3b452 +Create Date: 2026-04-25 17:47:56.374857 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1d3b2e5ca6a5' +down_revision = '070195b3b452' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('asignacion_rutina', + sa.Column('hijo_id', sa.Integer(), nullable=False), + sa.Column('rutina_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['hijo_id'], ['hijo.id'], ), + sa.ForeignKeyConstraint(['rutina_id'], ['rutina.id'], ), + sa.PrimaryKeyConstraint('hijo_id', 'rutina_id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('asignacion_rutina') + # ### end Alembic commands ### diff --git a/migrations/versions/3b972ef6198c_.py b/migrations/versions/3b972ef6198c_.py new file mode 100644 index 0000000000..63f7c2c5df --- /dev/null +++ b/migrations/versions/3b972ef6198c_.py @@ -0,0 +1,60 @@ +"""empty message + +Revision ID: 3b972ef6198c +Revises: bdc8d44ab3ac +Create Date: 2026-04-23 09:55:28.882333 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '3b972ef6198c' +down_revision = 'bdc8d44ab3ac' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('hijo', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('nombre', sa.String(length=80), nullable=False), + sa.Column('apellido', sa.String(length=80), nullable=False), + sa.Column('edad', sa.Integer(), nullable=False), + sa.Column('foto_url', sa.String(length=255), nullable=True), + sa.Column('info_adicional', sa.Text(), nullable=True), + sa.Column('intolerancia', sa.String(length=100), nullable=True), + sa.Column('alergia', sa.String(length=100), nullable=True), + sa.Column('asma', sa.String(length=50), nullable=True), + sa.Column('tipo_sangre', sa.String(length=10), nullable=False), + sa.Column('gatea', sa.String(length=10), nullable=True), + sa.Column('autonomia_bano', sa.String(length=10), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('autorizado', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('nombre', sa.String(length=80), nullable=False), + sa.Column('apellidos', sa.String(length=80), nullable=False), + sa.Column('dni', sa.String(length=20), nullable=False), + sa.Column('telefono', sa.String(length=20), nullable=False), + sa.Column('parentesco', sa.String(length=50), nullable=True), + sa.Column('foto_url', sa.String(length=255), nullable=True), + sa.Column('es_permanente', sa.Boolean(), nullable=True), + sa.Column('valido_desde', sa.String(length=20), nullable=True), + sa.Column('valido_hasta', sa.String(length=20), nullable=True), + sa.Column('hijo_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['hijo_id'], ['hijo.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('autorizado') + op.drop_table('hijo') + # ### end Alembic commands ### diff --git a/migrations/versions/ba08d68592c6_.py b/migrations/versions/ba08d68592c6_.py new file mode 100644 index 0000000000..08567e444b --- /dev/null +++ b/migrations/versions/ba08d68592c6_.py @@ -0,0 +1,37 @@ +"""empty message + +Revision ID: ba08d68592c6 +Revises: 1d3b2e5ca6a5 +Create Date: 2026-04-27 07:06:31.747029 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ba08d68592c6' +down_revision = '1d3b2e5ca6a5' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('rutina_compartida', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('cuidador_id', sa.Integer(), nullable=False), + sa.Column('rutina_id', sa.Integer(), nullable=False), + sa.Column('hijo_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['cuidador_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['hijo_id'], ['hijo.id'], ), + sa.ForeignKeyConstraint(['rutina_id'], ['rutina.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('rutina_compartida') + # ### end Alembic commands ### diff --git a/migrations/versions/bdc8d44ab3ac_.py b/migrations/versions/bdc8d44ab3ac_.py new file mode 100644 index 0000000000..748d79c870 --- /dev/null +++ b/migrations/versions/bdc8d44ab3ac_.py @@ -0,0 +1,44 @@ +"""empty message + +Revision ID: bdc8d44ab3ac +Revises: 0763d677d453 +Create Date: 2026-04-14 21:18:15.093608 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'bdc8d44ab3ac' +down_revision = '0763d677d453' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('nombre', sa.String(length=80), nullable=False)) + batch_op.add_column(sa.Column('apellidos', sa.String(length=80), nullable=False)) + batch_op.add_column(sa.Column('edad', sa.Integer(), nullable=False)) + batch_op.add_column(sa.Column('direccion_hogar', sa.String(length=200), nullable=True)) + batch_op.add_column(sa.Column('direccion_trabajo', sa.String(length=200), nullable=True)) + batch_op.add_column(sa.Column('telefono', sa.String(length=20), nullable=True)) + batch_op.add_column(sa.Column('foto_perfil', sa.String(length=255), nullable=True)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_column('foto_perfil') + batch_op.drop_column('telefono') + batch_op.drop_column('direccion_trabajo') + batch_op.drop_column('direccion_hogar') + batch_op.drop_column('edad') + batch_op.drop_column('apellidos') + batch_op.drop_column('nombre') + + # ### end Alembic commands ### diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..28f53b3d77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,17 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/material": "^9.0.0", + "@mui/x-date-pickers": "^9.0.2", + "dayjs": "^1.11.20", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react-google-autocomplete": "^2.7.5", + "react-router-dom": "^6.18.0", + "sweetalert2": "^11.26.24" }, "devDependencies": { "@types/react": "^18.2.18", @@ -46,7 +53,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", @@ -109,7 +115,6 @@ "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.26.5", @@ -160,7 +165,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", @@ -202,7 +206,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -212,7 +215,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -246,7 +248,6 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.26.7" @@ -290,11 +291,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.25.9", @@ -309,7 +318,6 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -328,7 +336,6 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -338,6 +345,152 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -846,7 +999,6 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -861,7 +1013,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -871,43 +1022,334 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/core-downloads-tracker": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.0.tgz", + "integrity": "sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz", + "integrity": "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.0.0", + "@mui/system": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.4", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^9.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "license": "MIT" + }, + "node_modules/@mui/private-theming": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.0.0.tgz", + "integrity": "sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.0.0.tgz", + "integrity": "sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.0.0.tgz", + "integrity": "sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.0.0", + "@mui/styled-engine": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.0.0.tgz", + "integrity": "sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0.tgz", + "integrity": "sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==", + "license": "MIT" + }, + "node_modules/@mui/x-date-pickers": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-9.0.2.tgz", + "integrity": "sha512-rnyc2wFPnprTS5i8Lq9aX2Rlx+ZRvNZERTd7sPMErf/8HnMCYzxwErITbd+TuImlvo2vaLF1gNynqT8AJMusYw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0", + "@mui/x-internals": "^9.0.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^7.3.0 || ^9.0.0", + "@mui/system": "^7.3.0 || ^9.0.0", + "date-fns": "^2.25.0 || ^3.2.0 || ^4.0.0", + "date-fns-jalali": "^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0", + "dayjs": "^1.10.7", + "luxon": "^3.0.2", + "moment": "^2.29.4", + "moment-hijri": "^2.1.2 || ^3.0.0", + "moment-jalaali": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "date-fns": { + "optional": true + }, + "date-fns-jalali": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + }, + "moment-hijri": { + "optional": true + }, + "moment-jalaali": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.0.0.tgz", + "integrity": "sha512-E/4rdg69JjhyybpPGypCjAKSKLLnSdCFM+O6P/nkUg47+qt3uftxQEhjQO53rcn6ahHl6du/uNZ9BLgeY6kYxQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -943,6 +1385,16 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@remix-run/router": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", @@ -997,26 +1449,22 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true, + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -1033,6 +1481,15 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1258,6 +1715,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1307,14 +1779,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -1369,7 +1833,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -1395,12 +1858,43 @@ ], "license": "CC-BY-4.0" }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1417,10 +1911,9 @@ } }, "node_modules/csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/data-view-buffer": { @@ -1477,11 +1970,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1549,6 +2047,16 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1571,6 +2079,15 @@ "dev": true, "license": "ISC" }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -1651,7 +2168,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -1790,6 +2306,18 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -1999,18 +2527,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -2252,6 +2768,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -2311,7 +2833,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2438,7 +2959,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "engines": { "node": ">=4" } @@ -2555,7 +3075,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -2564,6 +3083,15 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2578,7 +3106,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2648,6 +3175,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -2718,7 +3251,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -3052,7 +3584,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3061,6 +3592,12 @@ "node": ">=6" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3113,6 +3650,18 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3156,8 +3705,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/nanoid": { "version": "3.3.8", @@ -3344,7 +3892,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -3352,6 +3899,24 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3382,14 +3947,21 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/possible-typed-array-names": { @@ -3506,6 +4078,19 @@ "react": "^18.3.1" } }, + "node_modules/react-google-autocomplete": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/react-google-autocomplete/-/react-google-autocomplete-2.7.5.tgz", + "integrity": "sha512-CPHMFBCgaJER19iA/y9Isjxh5au8mRxkt6przu9miOHLqrno+ifwJGXQOzMvtwdqNPXhb5Z6jpTtiJrbFeUvHg==", + "license": "ISC", + "dependencies": { + "lodash.debounce": "^4.0.8", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -3553,6 +4138,22 @@ "react-dom": ">=16.8" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -3586,9 +4187,36 @@ "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" @@ -3601,7 +4229,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -3905,35 +4532,21 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, - "optional": true, - "peer": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -4061,11 +4674,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4074,35 +4692,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" + "node_modules/sweetalert2": { + "version": "11.26.24", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.24.tgz", + "integrity": "sha512-SLgukW4wicewpW5VOukSXY5Z6DL/z7HCOK2ODSjmQPiSphCN8gJAmh9npoceXOtBRNoDN0xIz+zHYthtfiHmjg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" } }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4272,6 +4871,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vite": { "version": "4.5.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", @@ -4454,6 +5062,15 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4482,7 +5099,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -4530,7 +5146,6 @@ "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", - "dev": true, "requires": { "@babel/parser": "^7.26.5", "@babel/types": "^7.26.5", @@ -4573,7 +5188,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "requires": { "@babel/traverse": "^7.25.9", "@babel/types": "^7.25.9" @@ -4599,14 +5213,12 @@ "@babel/helper-string-parser": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/helper-validator-option": { "version": "7.25.9", @@ -4628,7 +5240,6 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", - "dev": true, "requires": { "@babel/types": "^7.26.7" } @@ -4651,11 +5262,15 @@ "@babel/helper-plugin-utils": "^7.25.9" } }, + "@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==" + }, "@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", - "dev": true, "requires": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", @@ -4666,7 +5281,6 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", - "dev": true, "requires": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.5", @@ -4681,12 +5295,125 @@ "version": "7.26.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", - "dev": true, "requires": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, + "@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "requires": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "requires": { + "@emotion/memoize": "^0.9.0" + } + }, + "@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "requires": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + } + }, + "@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -4925,7 +5652,6 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -4935,43 +5661,149 @@ "@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==" }, "@jridgewell/set-array": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" }, "@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@mui/core-downloads-tracker": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.0.0.tgz", + "integrity": "sha512-uwQNGkhv0lf7ufxw6QXev77BW6pWbW+7uxYjU5+rfp4lBkFtMEgJCsarTM3Tn+i0lGx6+Ol2u88JdGXr0GDskA==" + }, + "@mui/material": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.0.0.tgz", + "integrity": "sha512-+VP/oQCDhDR87NQQgXnNBG8dwy6GNuQLnenS1pZvkbn2dKFSxRSRMybTpH9xUxXP+316mlYDy5CSbYtusnCWtw==", + "requires": { + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.0.0", + "@mui/system": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.4", + "react-transition-group": "^4.4.5" + }, + "dependencies": { + "react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==" + } + } + }, + "@mui/private-theming": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.0.0.tgz", + "integrity": "sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA==", + "requires": { + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.0.0", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.0.0.tgz", + "integrity": "sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==", + "requires": { + "@babel/runtime": "^7.29.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.0.0.tgz", + "integrity": "sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g==", + "requires": { + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.0.0", + "@mui/styled-engine": "^9.0.0", + "@mui/types": "^9.0.0", + "@mui/utils": "^9.0.0", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.0.0.tgz", + "integrity": "sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==", + "requires": { + "@babel/runtime": "^7.29.2" + } + }, + "@mui/utils": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.0.0.tgz", + "integrity": "sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==", + "requires": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.0.0", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.4" + }, + "dependencies": { + "react-is": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.5.tgz", + "integrity": "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==" + } + } + }, + "@mui/x-date-pickers": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-9.0.2.tgz", + "integrity": "sha512-rnyc2wFPnprTS5i8Lq9aX2Rlx+ZRvNZERTd7sPMErf/8HnMCYzxwErITbd+TuImlvo2vaLF1gNynqT8AJMusYw==", + "requires": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0", + "@mui/x-internals": "^9.0.0", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + } + }, + "@mui/x-internals": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-9.0.0.tgz", + "integrity": "sha512-E/4rdg69JjhyybpPGypCjAKSKLLnSdCFM+O6P/nkUg47+qt3uftxQEhjQO53rcn6ahHl6du/uNZ9BLgeY6kYxQ==", + "requires": { + "@babel/runtime": "^7.28.6", + "@mui/utils": "9.0.0", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4998,6 +5830,11 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" + }, "@remix-run/router": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", @@ -5044,25 +5881,20 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true + "@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" }, "@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5075,6 +5907,12 @@ "dev": true, "requires": {} }, + "@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "requires": {} + }, "@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -5224,6 +6062,16 @@ "possible-typed-array-names": "^1.0.0" } }, + "babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "requires": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5252,14 +6100,6 @@ "update-browserslist-db": "^1.1.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -5295,8 +6135,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "caniuse-lite": { "version": "1.0.30001697", @@ -5304,12 +6143,34 @@ "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", "dev": true }, + "clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + } + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5322,10 +6183,9 @@ } }, "csstype": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, "data-view-buffer": { "version": "1.0.2", @@ -5360,11 +6220,15 @@ "is-data-view": "^1.0.1" } }, + "dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==" + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -5406,6 +6270,15 @@ "esutils": "^2.0.2" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5423,6 +6296,14 @@ "integrity": "sha512-M+29jTcfNNoR9NV7la4SwUqzWAxEwnc7ThA5e1m6LRSotmpfpCpLcIfgtSCVL+MllNLgAyM/5ru86iMRemPzDQ==", "dev": true }, + "error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -5491,8 +6372,7 @@ "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-iterator-helpers": { "version": "1.2.1", @@ -5595,6 +6475,11 @@ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, "eslint": { "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", @@ -5675,12 +6560,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, "eslint-visitor-keys": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", @@ -5916,6 +6795,11 @@ "flat-cache": "^3.0.4" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -5957,8 +6841,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.8", @@ -6042,8 +6925,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globalthis": { "version": "1.0.4", @@ -6110,11 +6992,18 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "requires": { "function-bind": "^1.1.2" } }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, "ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6125,7 +7014,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -6175,6 +7063,11 @@ "get-intrinsic": "^1.2.6" } }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -6217,7 +7110,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "requires": { "hasown": "^2.0.2" } @@ -6420,8 +7312,12 @@ "jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema-traverse": { "version": "0.4.1", @@ -6461,6 +7357,16 @@ "type-check": "~0.4.0" } }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6493,8 +7399,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "nanoid": { "version": "3.3.8", @@ -6618,11 +7523,21 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6644,14 +7559,17 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "possible-typed-array-names": { "version": "1.0.0", @@ -6715,6 +7633,15 @@ "scheduler": "^0.23.2" } }, + "react-google-autocomplete": { + "version": "2.7.5", + "resolved": "https://registry.npmjs.org/react-google-autocomplete/-/react-google-autocomplete-2.7.5.tgz", + "integrity": "sha512-CPHMFBCgaJER19iA/y9Isjxh5au8mRxkt6przu9miOHLqrno+ifwJGXQOzMvtwdqNPXhb5Z6jpTtiJrbFeUvHg==", + "requires": { + "lodash.debounce": "^4.0.8", + "prop-types": "^15.5.0" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6743,6 +7670,17 @@ "react-router": "6.29.0" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6773,11 +7711,26 @@ "set-function-name": "^2.0.2" } }, + "reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, + "resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "requires": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" }, "reusify": { "version": "1.0.4", @@ -6976,34 +7929,17 @@ "side-channel-map": "^1.0.1" } }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -7088,35 +8024,20 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" }, - "terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - } - } + "sweetalert2": { + "version": "11.26.24", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.24.tgz", + "integrity": "sha512-SLgukW4wicewpW5VOukSXY5Z6DL/z7HCOK2ODSjmQPiSphCN8gJAmh9npoceXOtBRNoDN0xIz+zHYthtfiHmjg==" }, "text-table": { "version": "0.2.0", @@ -7223,6 +8144,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "requires": {} + }, "vite": { "version": "4.5.9", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", @@ -7324,6 +8251,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==" + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 0caab10749..31bc9718bb 100755 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "main": "index.js", "scripts": { "dev": "vite", - "start": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "start": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" }, "author": { "name": "Alejandro Sanchez", @@ -30,13 +30,13 @@ "license": "ISC", "devDependencies": { "@types/react": "^18.2.18", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.46.0", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.46.0", + "eslint-plugin-react": "^7.33.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "vite": "^4.4.8" }, "babel": { "presets": [ @@ -54,9 +54,16 @@ ] }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/material": "^9.0.0", + "@mui/x-date-pickers": "^9.0.2", + "dayjs": "^1.11.20", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-google-autocomplete": "^2.7.5", + "react-router-dom": "^6.18.0", + "sweetalert2": "^11.26.24" } } diff --git a/public/4geeks.ico b/public/4geeks.jpg similarity index 100% rename from public/4geeks.ico rename to public/4geeks.jpg diff --git a/public/Favicon.png b/public/Favicon.png new file mode 100644 index 0000000000..ac072aedf9 Binary files /dev/null and b/public/Favicon.png differ diff --git a/render_build.sh b/render_build.sh index 404821a383..f5456a1397 100644 --- a/render_build.sh +++ b/render_build.sh @@ -5,6 +5,9 @@ set -o errexit npm install npm run build -pipenv install +pip install --upgrade pip +pip install pipenv + +pipenv install --dev --deploy --ignore-pipfile pipenv run upgrade diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..a48fc5c73f 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,155 @@ from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column db = SQLAlchemy() -class User(db.Model): - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) +asignacion_rutina = db.Table('asignacion_rutina', + db.Column('hijo_id', db.Integer, db.ForeignKey('hijo.id'), primary_key=True), + db.Column('rutina_id', db.Integer, db.ForeignKey('rutina.id'), primary_key=True) +) +class User(db.Model): + __tablename__ = 'user' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(120), unique=True, nullable=False) + password = db.Column(db.String(80), unique=False, nullable=False) + nombre = db.Column(db.String(80), nullable=False) + apellidos = db.Column(db.String(80), nullable=False) + edad = db.Column(db.Integer, nullable=False) + direccion_hogar = db.Column(db.String(200)) + direccion_trabajo = db.Column(db.String(200)) + telefono = db.Column(db.String(20)) + foto_perfil = db.Column(db.String(255)) + is_active = db.Column(db.Boolean(), unique=False, nullable=False, default=True) + + hijos = db.relationship('Hijo', backref='parent', lazy=True) def serialize(self): return { "id": self.id, "email": self.email, - # do not serialize the password, its a security breach + "nombre": self.nombre, + "apellidos": self.apellidos, + "foto_perfil": self.foto_perfil + } + +class Hijo(db.Model): + __tablename__ = 'hijo' + id = db.Column(db.Integer, primary_key=True) + nombre = db.Column(db.String(80), nullable=False) + apellido = db.Column(db.String(80), nullable=False) + edad = db.Column(db.Integer, nullable=False) + foto_url = db.Column(db.String(255)) + info_adicional = db.Column(db.Text) + intolerancia = db.Column(db.String(100), default="Ninguna") + alergia = db.Column(db.String(100), default="Ninguna") + asma = db.Column(db.String(50), default="No") + tipo_sangre = db.Column(db.String(10), nullable=False) + gatea = db.Column(db.String(10), default="No") + autonomia_bano = db.Column(db.String(10), default="No") + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + + autorizados = db.relationship('Autorizado', backref='hijo', lazy=True) + rutinas = db.relationship('Rutina', secondary=asignacion_rutina, backref=db.backref('hijos_asignados', lazy='dynamic')) + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "apellido": self.apellido, + "edad": self.edad, + "fotoUrl": self.foto_url, + "datosMedicos": { + "intolerancia": self.intolerancia, + "alergia": self.alergia, + "asma": self.asma, + "tipoSangre": self.tipo_sangre + }, + "desarrollo": { + "gatea": self.gatea, + "autonomiaBano": self.autonomia_bano + }, + "info": self.info_adicional + } + +class Autorizado(db.Model): + __tablename__ = 'autorizado' + id = db.Column(db.Integer, primary_key=True) + nombre = db.Column(db.String(80), nullable=False) + apellidos = db.Column(db.String(80), nullable=False) + dni = db.Column(db.String(20), nullable=False) + telefono = db.Column(db.String(20), nullable=False) + parentesco = db.Column(db.String(50)) + foto_url = db.Column(db.String(255)) + es_permanente = db.Column(db.Boolean, default=True) + valido_desde = db.Column(db.String(20)) + valido_hasta = db.Column(db.String(20)) + hijo_id = db.Column(db.Integer, db.ForeignKey('hijo.id'), nullable=False) + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "apellidos": self.apellidos, + "dni": self.dni, + "telefono": self.telefono, + "parentesco": self.parentesco, + "fotoUrl": self.foto_url, + "esPermanente": self.es_permanente, + "validoDesde": self.valido_desde, + "validoHasta": self.valido_hasta, + "hijoId": self.hijo_id + } + +class Rutina(db.Model): + __tablename__ = 'rutina' + id = db.Column(db.Integer, primary_key=True) + nombre = db.Column(db.String(120), nullable=False) + detalles = db.Column(db.String(250), nullable=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + + actividades = db.relationship('Actividad', backref='rutina', lazy=True, cascade="all, delete-orphan") + + def serialize(self): + return { + "id": self.id, + "nombre": self.nombre, + "detalles": self.detalles, + "user_id": self.user_id + } + +class Actividad(db.Model): + __tablename__ = 'actividad' + id = db.Column(db.Integer, primary_key=True) + text = db.Column(db.String(250), nullable=False) + time = db.Column(db.String(50), nullable=False) + category = db.Column(db.String(50), nullable=False) + rutina_id = db.Column(db.Integer, db.ForeignKey('rutina.id'), nullable=False) + + def serialize(self): + return { + "id": self.id, + "text": self.text, + "time": self.time, + "category": self.category, + "rutina_id": self.rutina_id + } + +class RutinaCompartida(db.Model): + __tablename__ = 'rutina_compartida' + id = db.Column(db.Integer, primary_key=True) + cuidador_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + rutina_id = db.Column(db.Integer, db.ForeignKey('rutina.id'), nullable=False) + hijo_id = db.Column(db.Integer, db.ForeignKey('hijo.id'), nullable=False) + + + cuidador = db.relationship('User', foreign_keys=[cuidador_id]) + rutina_rel = db.relationship('Rutina') + hijo_rel = db.relationship('Hijo') + + def serialize(self): + return { + "id": self.id, + "rutina": self.rutina_rel.serialize(), + "hijo": self.hijo_rel.serialize(), + "cuidador": self.cuidador.serialize() } \ No newline at end of file diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..85c40b0221 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,22 +1,340 @@ -""" -This module takes care of starting the API Server, Loading the DB and Adding the endpoints -""" -from flask import Flask, request, jsonify, url_for, Blueprint -from api.models import db, User -from api.utils import generate_sitemap, APIException -from flask_cors import CORS +from flask import Flask, request, jsonify, Blueprint +from api.models import db, User, Hijo, Autorizado, Rutina, Actividad, RutinaCompartida +from flask_jwt_extended import jwt_required, get_jwt_identity, create_access_token api = Blueprint('api', __name__) -# Allow CORS requests to this API -CORS(api) +@api.route('/login', methods=['POST']) +def handle_login(): + body = request.get_json() + if not body or "email" not in body or "password" not in body: + return jsonify({"msg": "Faltan credenciales"}), 400 + user = User.query.filter_by(email=body['email'], password=body['password']).first() + + if user is None: + return jsonify({"msg": "Correo o contraseña incorrectos"}), 401 + + access_token = create_access_token(identity=str(user.id)) + + return jsonify({ + "msg": "Login exitoso", + "token": access_token, + "user": user.serialize() + }), 200 -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): - response_body = { - "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" - } +@api.route('/registro', methods=['POST']) +def handle_registro(): + body = request.get_json() + if body is None: + return jsonify({"msg": "No se recibió información"}), 400 + + user_exists = User.query.filter_by(email=body.get('email')).first() + if user_exists: + return jsonify({"msg": "El correo electrónico ya está registrado"}), 400 - return jsonify(response_body), 200 + nuevo_usuario = User( + email=body.get('email'), + password=body.get('password'), + nombre=body.get('nombre'), + apellidos=body.get('apellidos'), + edad=body.get('edad'), + direccion_hogar=body.get('direccionHogar'), + direccion_trabajo=body.get('direccionTrabajo'), + telefono=body.get('telefonoCompleto'), + foto_perfil=body.get('fotoPerfil'), + is_active=True + ) + + db.session.add(nuevo_usuario) + try: + db.session.commit() + return jsonify({"msg": "Usuario creado con éxito"}), 201 + except Exception as e: + db.session.rollback() + print(f"Error en servidor: {str(e)}") + return jsonify({"msg": "Error interno del servidor", "error": str(e)}), 500 + +@api.route('/hijos', methods=['POST']) +@jwt_required() +def add_hijo(): + current_user_id = get_jwt_identity() + body = request.get_json() + datos_medicos = body.get("datosMedicos", {}) + desarrollo = body.get("desarrollo", {}) + + nuevo_hijo = Hijo( + nombre=body['nombre'], + apellido=body['apellido'], + edad=body['edad'], + foto_url=body.get('fotoUrl'), + info_adicional=body.get('info'), + intolerancia=datos_medicos.get('intolerancia'), + alergia=datos_medicos.get('alergia'), + asma=datos_medicos.get('asma'), + tipo_sangre=datos_medicos.get('tipoSangre'), + gatea=desarrollo.get('gatea'), + autonomia_bano=desarrollo.get('autonomiaBano'), + user_id=current_user_id + ) + db.session.add(nuevo_hijo) + try: + db.session.commit() + return jsonify({"msg": "Hijo registrado", "hijo": nuevo_hijo.serialize()}), 201 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error", "error": str(e)}), 500 + +@api.route('/hijos/', methods=['DELETE']) +@jwt_required() +def delete_hijo(hijo_id): + current_user_id = get_jwt_identity() + hijo = Hijo.query.filter_by(id=hijo_id, user_id=current_user_id).first() + + if not hijo: + return jsonify({"msg": "Hijo no encontrado o no tienes permiso"}), 404 + + try: + RutinaCompartida.query.filter_by(hijo_id=hijo_id).delete() + + Autorizado.query.filter_by(hijo_id=hijo_id).delete() + + hijo.rutinas = [] + + db.session.delete(hijo) + db.session.commit() + return jsonify({"msg": "Hijo y datos vinculados eliminados con éxito"}), 200 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error al eliminar", "error": str(e)}), 500 + +@api.route('/autorizados', methods=['POST']) +@jwt_required() +def add_autorizado(): + body = request.get_json() + h_id = body.get('hijoId') or body.get('hijo_id') + + nuevo_auth = Autorizado( + nombre=body['nombre'], apellidos=body['apellidos'], dni=body['dni'], + telefono=body['telefono'], parentesco=body.get('parentesco'), + foto_url=body.get('fotoUrl'), es_permanente=body.get('esPermanente', True), + valido_desde=body.get('validoDesde'), valido_hasta=body.get('validoHasta'), + hijo_id=h_id + ) + db.session.add(nuevo_auth) + try: + db.session.commit() + return jsonify({"msg": "Autorizado registrado", "autorizado": nuevo_auth.serialize()}), 201 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error", "error": str(e)}), 500 + +@api.route('/autorizados/', methods=['DELETE']) +@jwt_required() +def delete_autorizado(auth_id): + auth = Autorizado.query.get(auth_id) + if not auth: + return jsonify({"msg": "Autorizado no encontrado"}), 404 + + try: + db.session.delete(auth) + db.session.commit() + return jsonify({"msg": "Autorizado eliminado correctamente"}), 200 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error al eliminar", "error": str(e)}), 500 + +@api.route('/rutinas', methods=['GET', 'POST']) +@jwt_required() +def handle_rutinas(): + current_user_id = get_jwt_identity() + try: + if request.method == 'GET': + rutinas = Rutina.query.filter_by(user_id=current_user_id).all() + return jsonify([r.serialize() for r in rutinas]), 200 + if request.method == 'POST': + body = request.get_json() + if not body or not body.get("nombre"): + return jsonify({"msg": "El nombre es obligatorio"}), 400 + nueva_rutina = Rutina( + nombre=body.get("nombre"), + detalles=body.get("detalles", ""), + user_id=current_user_id + ) + db.session.add(nueva_rutina) + db.session.commit() + return jsonify(nueva_rutina.serialize()), 201 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error", "error": str(e)}), 500 + +@api.route('/asignar-rutina', methods=['POST']) +@jwt_required() +def asignar_rutina_a_hijos_modal(): + body = request.get_json() + rutina_id = body.get("rutina_id") + hijos_ids = body.get("hijo_ids") + + if not rutina_id or not hijos_ids: + return jsonify({"msg": "Datos insuficientes"}), 400 + + rutina = Rutina.query.get(rutina_id) + if not rutina: + return jsonify({"msg": "Rutina no encontrada"}), 404 + + hijos = Hijo.query.filter(Hijo.id.in_(hijos_ids)).all() + rutina.hijos_asignados = hijos + + try: + db.session.commit() + return jsonify({"msg": "Asignación exitosa"}), 200 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error", "error": str(e)}), 500 + +@api.route('/hijos//rutinas', methods=['GET']) +@jwt_required() +def get_rutinas_de_hijo(hijo_id): + hijo = Hijo.query.get(hijo_id) + if not hijo: + return jsonify({"msg": "Hijo no encontrado"}), 404 + return jsonify([r.serialize() for r in hijo.rutinas]), 200 + +@api.route('/rutinas/', methods=['DELETE']) +@jwt_required() +def delete_rutina(rutina_id): + current_user_id = get_jwt_identity() + rutina = Rutina.query.filter_by(id=rutina_id, user_id=current_user_id).first() + if not rutina: + return jsonify({"msg": "Rutina no encontrada"}), 404 + db.session.delete(rutina) + db.session.commit() + return jsonify({"msg": "Rutina eliminada"}), 200 + + +# --- RUTAS DE ACTIVIDADES --- + +@api.route('/rutinas//actividades', methods=['GET', 'POST']) +@jwt_required() +def handle_actividades(rutina_id): + current_user_id = get_jwt_identity() + rutina = Rutina.query.filter_by(id=rutina_id, user_id=current_user_id).first() + if not rutina: + return jsonify({"msg": "Rutina no encontrada"}), 404 + + if request.method == 'GET': + actividades = Actividad.query.filter_by(rutina_id=rutina_id).all() + return jsonify([a.serialize() for a in actividades]), 200 + + if request.method == 'POST': + try: + body = request.get_json() + nueva_actividad = Actividad( + text=body.get("text"), + time=body.get("time"), + category=body.get("category"), + rutina_id=rutina_id + ) + db.session.add(nueva_actividad) + db.session.commit() + return jsonify(nueva_actividad.serialize()), 201 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error al crear actividad", "error": str(e)}), 500 + +@api.route('/actividades/', methods=['PUT', 'DELETE']) +@jwt_required() +def update_delete_actividad(actividad_id): + actividad = Actividad.query.get(actividad_id) + if not actividad: + return jsonify({"msg": "Actividad no encontrada"}), 404 + + if request.method == 'PUT': + body = request.get_json() + actividad.text = body.get("text", actividad.text) + actividad.time = body.get("time", actividad.time) + actividad.category = body.get("category", actividad.category) + db.session.commit() + return jsonify(actividad.serialize()), 200 + + if request.method == 'DELETE': + db.session.delete(actividad) + db.session.commit() + return jsonify({"msg": "Actividad eliminada"}), 200 + + + +@api.route('/parent-data', methods=['GET']) +@jwt_required() +def get_parent_data(): + current_user_id = get_jwt_identity() + user = User.query.get(current_user_id) + if not user: return jsonify({"msg": "Usuario no encontrado"}), 404 + + hijos = Hijo.query.filter_by(user_id=current_user_id).all() + autorizados = db.session.query(Autorizado).join(Hijo).filter(Hijo.user_id == current_user_id).all() + + return jsonify({ + "hijos": [h.serialize() for h in hijos], + "autorizados": [a.serialize() for a in autorizados] + }), 200 + +@api.route('/rutinas/compartir', methods=['POST']) +@jwt_required() +def compartir_rutina(): + body = request.get_json() + email_cuidador = body.get("email") + rutina_id = body.get("rutina_id") + hijo_id = body.get("hijo_id") + + cuidador = User.query.filter_by(email=email_cuidador).first() + if not cuidador: + return jsonify({"msg": "El correo no pertenece a ningún usuario registrado"}), 404 + + existe = RutinaCompartida.query.filter_by( + cuidador_id=cuidador.id, + rutina_id=rutina_id, + hijo_id=hijo_id + ).first() + + if existe: + return jsonify({"msg": "Esta rutina ya ha sido compartida con este usuario"}), 400 + + nueva_asignacion = RutinaCompartida( + cuidador_id=cuidador.id, + rutina_id=rutina_id, + hijo_id=hijo_id + ) + + try: + db.session.add(nueva_asignacion) + db.session.commit() + return jsonify({"msg": "Rutina compartida con éxito"}), 200 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error al compartir", "error": str(e)}), 500 + +@api.route('/cuidador/rutinas', methods=['GET']) +@jwt_required() +def get_rutinas_cuidador(): + current_user_id = get_jwt_identity() + asignaciones = RutinaCompartida.query.filter_by(cuidador_id=current_user_id).all() + return jsonify([asignacion.serialize() for asignacion in asignaciones]), 200 + +@api.route('/cuidador/rutinas/', methods=['DELETE']) +@jwt_required() +def eliminar_rutina_compartida(asignacion_id): + current_user_id = get_jwt_identity() + asignacion = RutinaCompartida.query.filter_by(id=asignacion_id, cuidador_id=current_user_id).first() + + if not asignacion: + return jsonify({"msg": "No se encontró la asignación o no tienes permiso"}), 404 + + try: + db.session.delete(asignacion) + db.session.commit() + return jsonify({"msg": "Ya no tienes acceso a esta rutina"}), 200 + except Exception as e: + db.session.rollback() + return jsonify({"msg": "Error al eliminar la asignación", "error": str(e)}), 500 \ No newline at end of file diff --git a/src/api/utils.py b/src/api/utils.py index 9c18b4d596..e220dc3fd3 100644 --- a/src/api/utils.py +++ b/src/api/utils.py @@ -23,8 +23,6 @@ def has_no_empty_params(rule): def generate_sitemap(app): links = ['/admin/'] for rule in app.url_map.iter_rules(): - # Filter out rules we can't navigate to in a browser - # and rules that require parameters if "GET" in rule.methods and has_no_empty_params(rule): url = url_for(rule.endpoint, **(rule.defaults or {})) if "/admin/" not in url: diff --git a/src/app.py b/src/app.py index 1b3340c0fa..be293b7e1c 100644 --- a/src/app.py +++ b/src/app.py @@ -1,29 +1,28 @@ -""" -This module takes care of starting the API Server, Loading the DB and Adding the endpoints -""" import os from flask import Flask, request, jsonify, url_for, send_from_directory from flask_migrate import Migrate from flask_swagger import swagger +from flask_cors import CORS +from flask_jwt_extended import JWTManager from api.utils import APIException, generate_sitemap from api.models import db from api.routes import api from api.admin import setup_admin from api.commands import setup_commands -# from models import Person +static_file_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '../dist/') -ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" -static_file_dir = os.path.join(os.path.dirname( - os.path.realpath(__file__)), '../dist/') app = Flask(__name__) app.url_map.strict_slashes = False -# database condiguration +app.config["JWT_SECRET_KEY"] = "super-secret-key-change-me" +jwt = JWTManager(app) + +CORS(app) + db_url = os.getenv("DATABASE_URL") if db_url is not None: - app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace( - "postgres://", "postgresql://") + app.config['SQLALCHEMY_DATABASE_URI'] = db_url.replace("postgres://", "postgresql://") else: app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:////tmp/test.db" @@ -31,42 +30,28 @@ MIGRATE = Migrate(app, db, compare_type=True) db.init_app(app) -# add the admin setup_admin(app) - -# add the admin setup_commands(app) - -# Add all endpoints form the API with a "api" prefix app.register_blueprint(api, url_prefix='/api') -# Handle/serialize errors like a JSON object - - @app.errorhandler(APIException) def handle_invalid_usage(error): return jsonify(error.to_dict()), error.status_code -# generate sitemap with all your endpoints - +@app.route('/sitemap') +def sitemap(): + return generate_sitemap(app) @app.route('/') -def sitemap(): - if ENV == "development": - return generate_sitemap(app) +def index(): return send_from_directory(static_file_dir, 'index.html') -# any other endpoint will try to serve it like a static file @app.route('/', methods=['GET']) def serve_any_other_file(path): if not os.path.isfile(os.path.join(static_file_dir, path)): path = 'index.html' - response = send_from_directory(static_file_dir, path) - response.cache_control.max_age = 0 # avoid cache memory - return response - + return send_from_directory(static_file_dir, path) -# this only runs if `$ python src/main.py` is executed if __name__ == '__main__': PORT = int(os.environ.get('PORT', 3001)) - app.run(host='0.0.0.0', port=PORT, debug=True) + app.run(host='0.0.0.0', port=PORT, debug=True) \ No newline at end of file diff --git a/src/front/actions.js b/src/front/actions.js new file mode 100644 index 0000000000..1f66feef31 --- /dev/null +++ b/src/front/actions.js @@ -0,0 +1,151 @@ +const backendUrl = import.meta.env.VITE_BACKEND_URL; + +export const actions = (store, dispatch) => { + return { + loadParentData: async () => { + const token = localStorage.getItem("token"); + if (!token) return; + + try { + + const resp = await fetch(`${backendUrl}/api/parent-data`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + } + }); + + if (!resp.ok) throw new Error("Error cargando datos"); + const data = await resp.json(); + + dispatch({ type: 'set_hijos', payload: data.hijos }); + dispatch({ type: 'set_autorizados', payload: data.autorizados }); + } catch (error) { + console.error("Error en loadParentData:", error); + } + }, + + addHijo: async (hijoData) => { + const token = localStorage.getItem("token"); + try { + const resp = await fetch(`${backendUrl}/api/hijos`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify(hijoData) + }); + if (resp.ok) { + const data = await resp.json(); + dispatch({ type: 'add_hijo', payload: data.hijo }); + return true; + } + return false; + } catch (error) { + console.error("Error al añadir hijo:", error); + return false; + } + }, + + deleteHijo: async (hijoId) => { + const token = localStorage.getItem("token"); + try { + const resp = await fetch(`${backendUrl}/api/hijos/${hijoId}`, { + method: "DELETE", + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (resp.ok) { + dispatch({ type: 'delete_hijo', payload: hijoId }); + + const respData = await fetch(`${backendUrl}/api/parent-data`, { + headers: { "Authorization": `Bearer ${token}` } + }); + if (respData.ok) { + const data = await respData.json(); + dispatch({ type: 'set_autorizados', payload: data.autorizados }); + } + return true; + } + + const errorData = await resp.json(); + console.error("Respuesta del servidor:", errorData.msg); + return false; + } catch (error) { + console.error("Error de red eliminando hijo:", error); + return false; + } + }, + + addAutorizado: async (authData) => { + const token = localStorage.getItem("token"); + try { + const resp = await fetch(`${backendUrl}/api/autorizados`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify(authData) + }); + if (resp.ok) { + const data = await resp.json(); + dispatch({ type: 'add_autorizado', payload: data.autorizado }); + return true; + } + return false; + } catch (error) { + console.error("Error al añadir autorizado:", error); + return false; + } + }, + + deleteAutorizado: async (authId) => { + const token = localStorage.getItem("token"); + try { + const resp = await fetch(`${backendUrl}/api/autorizados/${authId}`, { + method: "DELETE", + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (resp.ok) { + dispatch({ type: 'delete_autorizado', payload: authId }); + return true; + } + return false; + } catch (error) { + console.error("Error eliminando autorizado:", error); + return false; + } + }, + + deleteRutinaCompartida: async (asignacionId) => { + const token = localStorage.getItem("token"); + try { + const resp = await fetch(`${backendUrl}/api/cuidador/rutinas/${asignacionId}`, { + method: "DELETE", + headers: { + "Authorization": `Bearer ${token}` + } + }); + + if (resp.ok) { + return true; + } + + const errorData = await resp.json(); + console.error("Error del servidor:", errorData.msg); + return false; + } catch (error) { + console.error("Error de red eliminando rutina compartida:", error); + return false; + } + } + }; +}; \ No newline at end of file diff --git a/src/front/assets/Icono Baby Zzzync - fondo transparente.png b/src/front/assets/Icono Baby Zzzync - fondo transparente.png new file mode 100644 index 0000000000..51a789a3a7 Binary files /dev/null and b/src/front/assets/Icono Baby Zzzync - fondo transparente.png differ diff --git a/src/front/assets/Logo Baby Zzync 1 - vers blanca.png b/src/front/assets/Logo Baby Zzync 1 - vers blanca.png new file mode 100644 index 0000000000..c95cfec26f Binary files /dev/null and b/src/front/assets/Logo Baby Zzync 1 - vers blanca.png differ diff --git a/src/front/assets/Logo Baby Zzync 1.png b/src/front/assets/Logo Baby Zzync 1.png new file mode 100644 index 0000000000..fee054bdc4 Binary files /dev/null and b/src/front/assets/Logo Baby Zzync 1.png differ diff --git a/src/front/assets/Logo Baby Zzync 2.png b/src/front/assets/Logo Baby Zzync 2.png new file mode 100644 index 0000000000..0db0f2de5b Binary files /dev/null and b/src/front/assets/Logo Baby Zzync 2.png differ diff --git a/src/front/components/Cardautorizado.jsx b/src/front/components/Cardautorizado.jsx new file mode 100644 index 0000000000..e181bdbda1 --- /dev/null +++ b/src/front/components/Cardautorizado.jsx @@ -0,0 +1,179 @@ +import React from "react"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import Swal from 'sweetalert2'; + +export const Cardautorizado = ({ autorizado }) => { + const { store, actions } = useGlobalReducer(); + + const hijoVinculado = store.hijos?.find(h => h.id === autorizado.hijoId); + + const handleDelete = (e) => { + e.stopPropagation(); + + Swal.fire({ + title: '¿Eliminar?', + text: `Se borrará la autorización de ${autorizado.nombre} permanentemente.`, + icon: 'warning', + showCancelButton: true, + confirmButtonColor: 'var(--color-descanso)', + cancelButtonColor: '#c2c2c2', + confirmButtonText: 'Sí, eliminar', + cancelButtonText: 'Cancelar', + width: '400px', + customClass: { + popup: 'my-custom-popup', + confirmButton: 'rounded-pill px-3 shadow-sm', + cancelButton: 'rounded-pill px-3' + } + }).then(async (result) => { + if (result.isConfirmed) { + + const success = await actions.deleteAutorizado(autorizado.id); + + if (success) { + Swal.fire({ + title: 'Eliminado', + text: 'Registro de autorizado borrado.', + icon: 'success', + timer: 1500, + showConfirmButton: false, + width: '280px', + customClass: { popup: 'my-custom-popup' } + }); + } else { + Swal.fire({ + title: 'Error', + text: 'No se pudo eliminar el registro del servidor.', + icon: 'error' + }); + } + } + }); + }; + + return ( +
+
+ + +
+
+ {autorizado.nombre} +
+ +
+
+ {autorizado.nombre} {autorizado.apellidos} +
+ +

+ + Recoge a: {hijoVinculado ? hijoVinculado.nombre : "Hijo no encontrado"} +

+ +

+ {autorizado.parentesco || "Autorizado"} + {autorizado.esPermanente && Permanente} +

+ + +
+
+ + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/Cardhijo.jsx b/src/front/components/Cardhijo.jsx new file mode 100644 index 0000000000..f8d8ac47d0 --- /dev/null +++ b/src/front/components/Cardhijo.jsx @@ -0,0 +1,158 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import Swal from 'sweetalert2'; + +export const Cardhijo = ({ hijo }) => { + const { store, actions } = useGlobalReducer(); + const navigate = useNavigate(); + + const handleDelete = (e) => { + e.stopPropagation(); + Swal.fire({ + title: '¿Eliminar a ' + hijo.nombre + '?', + text: "Esta acción borrará al niño y a sus autorizados de forma permanente.", + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#ff6b6b', + cancelButtonColor: '#c2c2c2', + confirmButtonText: 'Sí, eliminar', + cancelButtonText: 'Cancelar', + width: '400px', + padding: '1.5rem', + customClass: { + popup: 'my-custom-popup', + confirmButton: 'rounded-pill px-3 shadow-sm', + cancelButton: 'rounded-pill px-3' + } + }).then(async (result) => { + if (result.isConfirmed) { + const success = await actions.deleteHijo(hijo.id); + if (success) { + Swal.fire({ + title: '¡Eliminado!', + text: 'El registro ha sido borrado.', + icon: 'success', + timer: 1500, + showConfirmButton: false, + width: '400px', + customClass: { popup: 'my-custom-popup' } + }); + } else { + Swal.fire({ + title: 'Error', + text: 'No se pudo eliminar el registro del servidor.', + icon: 'error', + confirmButtonColor: '#4CC9F0' + }); + } + } + }); + }; + + const tieneIntolerancia = hijo.datosMedicos?.intolerancia && hijo.datosMedicos.intolerancia !== "Ninguna"; + const tieneAlergia = hijo.datosMedicos?.alergia && hijo.datosMedicos.alergia !== "Ninguna"; + const tieneAsma = hijo.datosMedicos?.asma && hijo.datosMedicos.asma !== "No"; + + return ( +
+
+ + + + +
+ {hijo.nombre} navigate("/asignar-rutina", { state: { hijo } })} + style={{ + width: "75px", + height: "75px", + borderRadius: "50%", + objectFit: "cover", + border: "3px solid white", + cursor: "pointer" + }} + /> +

+ {hijo.nombre} +

+ +
+ {tieneIntolerancia && } + {tieneAlergia && } + {tieneAsma && } +
+
+ + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/Carduser.jsx b/src/front/components/Carduser.jsx new file mode 100644 index 0000000000..c745280b0f --- /dev/null +++ b/src/front/components/Carduser.jsx @@ -0,0 +1,60 @@ +import React, { useState, useEffect } from "react"; + +export const Carduser = () => { + const [userData, setUserData] = useState({ + nombre: "Usuario Padre", + fotoUrl: null + }); + + useEffect(() => { + const storedUser = localStorage.getItem("user"); + + if (storedUser) { + const parsedUser = JSON.parse(storedUser); + setUserData({ + nombre: parsedUser.nombre || "Usuario Padre", + fotoUrl: parsedUser.foto_perfil || null + }); + } + }, []); + + return ( +
+ +
+ {userData.fotoUrl ? ( + User + ) : ( + + )} +
+ +
+

+ {userData.nombre} +

+

+ @{userData.nombre.toLowerCase().replace(/\s+/g, '')} +

+
+ +
+ ); +}; + +const avatarStyle = { + width: "50px", + height: "50px", + borderRadius: "50%", + backgroundColor: "var(--color-primario)", + overflow: "hidden", + flexShrink: 0 +}; + +const imgStyle = { + width: "100%", + height: "100%", + objectFit: "cover" +}; \ No newline at end of file diff --git a/src/front/components/Cloudinary.jsx b/src/front/components/Cloudinary.jsx new file mode 100644 index 0000000000..56db5c9719 --- /dev/null +++ b/src/front/components/Cloudinary.jsx @@ -0,0 +1,93 @@ +import React, { useState, useRef } from 'react'; + +const Cloudinary = ({ onImageUploaded }) => { + const preset_name = "Lmroch"; + const cloud_name = "dlcubj61o"; + + const [image, setImage] = useState(''); + const [loading, setLoading] = useState(false); + const [showMenu, setShowMenu] = useState(false); + + const fileInputRef = useRef(null); + const cameraInputRef = useRef(null); + + const uploadImage = async (e) => { + const files = e.target.files; + if (!files || files.length === 0) return; + + setShowMenu(false); + const data = new FormData(); + data.append('file', files[0]); + data.append('upload_preset', preset_name); + + setLoading(true); + + try { + const response = await fetch(`https://api.cloudinary.com/v1_1/${cloud_name}/image/upload`, { + method: 'POST', + body: data + }); + + const file = await response.json(); + const urlOptimizada = file.secure_url.replace('/upload/', '/upload/w_150,h_150,c_fill,g_face/'); + setImage(urlOptimizada); + + if(onImageUploaded) { + onImageUploaded(urlOptimizada); + } + + setLoading(false); + } catch (error) { + console.error('Error uploading image:', error); + setLoading(false); + } + }; + + return ( +
+
!loading && setShowMenu(!showMenu)} + > + {!image && !loading &&

+ Foto

} + {loading &&

Subiendo...

} + {image && !loading && ( + Perfil + )} +
+ + {showMenu && ( +
+ + +
+ )} + + + +
+ ); +} + +export default Cloudinary; \ No newline at end of file diff --git a/src/front/components/Footer.jsx b/src/front/components/Footer.jsx index f06302dbd2..4755bbde23 100644 --- a/src/front/components/Footer.jsx +++ b/src/front/components/Footer.jsx @@ -1,11 +1,5 @@ export const Footer = () => ( - +
+ © 2026 Baby Zzzync / Made with love for 4Geeks +
); diff --git a/src/front/components/GoogleInput.jsx b/src/front/components/GoogleInput.jsx new file mode 100644 index 0000000000..6ce91adaf1 --- /dev/null +++ b/src/front/components/GoogleInput.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import Autocomplete from "react-google-autocomplete"; + +const GoogleInput = ({ + placeholder, + icon, + onPlaceSelected, + onChange, + value, + required = false +}) => { + return ( +
+ + + +
+ ); +}; + +export default GoogleInput; \ No newline at end of file diff --git a/src/front/components/HeaderApp.jsx b/src/front/components/HeaderApp.jsx new file mode 100644 index 0000000000..82b661677b --- /dev/null +++ b/src/front/components/HeaderApp.jsx @@ -0,0 +1,71 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; + +export const HeaderApp = ({ showBackButton = false, onBackClick }) => { + const [showMenu, setShowMenu] = useState(false); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("user"); + localStorage.removeItem("token"); + navigate("/"); + }; + + const dropdownStyle = { + position: "absolute", + top: "70px", + right: "15px", + backgroundColor: "white", + borderRadius: "8px", + boxShadow: "0px 4px 12px rgba(0,0,0,0.1)", + zIndex: 1000, + display: showMenu ? "block" : "none", + overflow: "hidden" + }; + + const dropdownItemStyle = { + padding: "12px 20px", + width: "100%", + border: "none", + backgroundColor: "transparent", + textAlign: "left", + fontSize: "0.9rem", + fontWeight: "bold", + cursor: "pointer", + borderBottom: "1px solid #f0f0f0" + }; + + return ( +
+ +
+ {showBackButton && ( + + )} +
+ + Logo Baby Zzzync + + setShowMenu(!showMenu)} + > + +
+ + + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/HeaderApp2.jsx b/src/front/components/HeaderApp2.jsx new file mode 100644 index 0000000000..0f9ba48beb --- /dev/null +++ b/src/front/components/HeaderApp2.jsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; + +export const HeaderApp2 = ({ showBackButton = false, onBackClick }) => { + const [showMenu, setShowMenu] = useState(false); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("user"); + localStorage.removeItem("token"); + navigate("/"); + }; + + const dropdownStyle = { + position: "absolute", + top: "70px", + right: "15px", + backgroundColor: "white", + borderRadius: "8px", + boxShadow: "0px 4px 12px rgba(0,0,0,0.1)", + zIndex: 1000, + display: showMenu ? "block" : "none", + overflow: "hidden" + }; + + const dropdownItemStyle = { + padding: "12px 20px", + width: "100%", + border: "none", + backgroundColor: "transparent", + textAlign: "left", + fontSize: "0.9rem", + fontWeight: "bold", + cursor: "pointer", + borderBottom: "1px solid #f0f0f0" + }; + + return ( +
+
+ {showBackButton ? ( + + ) : ( + navigate("/Home")} + > + )} +
+ +
+ Logo Baby Zzzync +
+ +
+ setShowMenu(!showMenu)} + > +
+ +
+ + + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/HeaderApp3.jsx b/src/front/components/HeaderApp3.jsx new file mode 100644 index 0000000000..50d3089660 --- /dev/null +++ b/src/front/components/HeaderApp3.jsx @@ -0,0 +1,112 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; + +export const HeaderApp3 = ({ showBackButton = false, onBackClick }) => { + const [showMenu, setShowMenu] = useState(false); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("user"); + localStorage.removeItem("token"); + navigate("/"); + }; + + const handleBack = () => { + if (onBackClick) { + onBackClick(); + } else { + navigate(-1); + } + }; + + const dropdownStyle = { + position: "absolute", + top: "70px", + right: "15px", + backgroundColor: "white", + borderRadius: "8px", + boxShadow: "0px 4px 12px rgba(0,0,0,0.1)", + zIndex: 1000, + display: showMenu ? "block" : "none", + overflow: "hidden" + }; + + const dropdownItemStyle = { + padding: "12px 20px", + width: "100%", + border: "none", + backgroundColor: "transparent", + textAlign: "left", + fontSize: "0.9rem", + fontWeight: "bold", + cursor: "pointer", + borderBottom: "1px solid #f0f0f0" + }; + + return ( +
+
+ {showBackButton && ( + + )} +
+ +
+ Logo Baby Zzzync +
+ +
+ setShowMenu(!showMenu)} + > +
+ +
+ + + + + +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/HeaderApp4.jsx b/src/front/components/HeaderApp4.jsx new file mode 100644 index 0000000000..ecebc95302 --- /dev/null +++ b/src/front/components/HeaderApp4.jsx @@ -0,0 +1,95 @@ +import React, { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; + +export const HeaderApp4 = ({ showBackButton = false, onBackClick }) => { + const [showMenu, setShowMenu] = useState(false); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("user"); + localStorage.removeItem("token"); + navigate("/"); + }; + + const dropdownStyle = { + position: "absolute", + top: "70px", + right: "15px", + backgroundColor: "white", + borderRadius: "8px", + boxShadow: "0px 4px 12px rgba(0,0,0,0.1)", + zIndex: 1000, + display: showMenu ? "block" : "none", + overflow: "hidden" + }; + + const dropdownItemStyle = { + padding: "12px 20px", + width: "100%", + border: "none", + backgroundColor: "transparent", + textAlign: "left", + fontSize: "0.9rem", + fontWeight: "bold", + cursor: "pointer", + borderBottom: "1px solid #f0f0f0" + }; + + return ( +
+
+ {showBackButton && ( + + )} +
+ +
+ Logo Baby Zzzync +
+ +
+ setShowMenu(!showMenu)} + > +
+ +
+ + + +
+
+ ); +}; \ No newline at end of file diff --git "a/src/front/components/InfoNi\303\261o.jsx" "b/src/front/components/InfoNi\303\261o.jsx" new file mode 100644 index 0000000000..948c15499c --- /dev/null +++ "b/src/front/components/InfoNi\303\261o.jsx" @@ -0,0 +1,48 @@ +import React from "react"; + +export const InfoNiño = ({ nombre, apellidos, edad, fotoUrl }) => { + return ( +
+ +
+
+

NOMBRE:

+

+ {nombre} +

+
+
+

APELLIDOS:

+

+ {apellidos} +

+
+
+

EDAD:

+

+ {edad} +

+
+
+ +
+
+ {fotoUrl ? ( + Niño + ) : ( + + FOTO
NIÑO +
+ )} +
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/components/ProtectedRoute.jsx b/src/front/components/ProtectedRoute.jsx new file mode 100644 index 0000000000..368ee63e11 --- /dev/null +++ b/src/front/components/ProtectedRoute.jsx @@ -0,0 +1,44 @@ +import React from "react"; +import { useNavigate, Navigate } from "react-router-dom"; + +const ProtectedRoute = ({ children }) => { + const navigate = useNavigate(); + const user = localStorage.getItem("user"); + + if (!user || user === "undefined" || user === "null") { + return ( + <> +
+ {children} +
+ +
+
+ +

Acceso Restringido

+

Debes iniciar sesión para gestionar tus rutinas.

+ + +
+
+ + ); + } + + return children; +}; + +export default ProtectedRoute; \ No newline at end of file diff --git a/src/front/hooks/useGlobalReducer.jsx b/src/front/hooks/useGlobalReducer.jsx index 6aeb9d768e..65bf22534b 100644 --- a/src/front/hooks/useGlobalReducer.jsx +++ b/src/front/hooks/useGlobalReducer.jsx @@ -1,6 +1,7 @@ // Import necessary hooks and functions from React. import { useContext, useReducer, createContext } from "react"; import storeReducer, { initialStore } from "../store" // Import the reducer and the initial state. +import { actions } from "../actions"; // Create a context to hold the global state of the application // We will call this global state the "store" to avoid confusion while using local states @@ -11,14 +12,18 @@ const StoreContext = createContext() export function StoreProvider({ children }) { // Initialize reducer with the initial state. const [store, dispatch] = useReducer(storeReducer, initialStore()) + + + const storeActions = actions(store, dispatch); + // Provide the store and dispatch method to all child components. - return + return {children} } // Custom hook to access the global state and dispatch function. export default function useGlobalReducer() { - const { dispatch, store } = useContext(StoreContext) - return { dispatch, store }; + const { dispatch, store, actions } = useContext(StoreContext) + return { dispatch, store, actions }; } \ No newline at end of file diff --git a/src/front/index.css b/src/front/index.css index e69de29bb2..f730651298 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -0,0 +1,132 @@ +:root { + --color-primario: #480CA8; + --color-secundario: #7209B7; + --color-alimentacion: #480CA8; + --color-descanso: #4CC9F0; + --color-cambio-pañal: #D65445; + --color-medicacion: #EDD03E; + --color-tiempo-de-ejercicio: #57D23B; + --color-fondo: #c9c9c9; + --color-fondoBotones: #bce9f5; +} + +.bg-registro { + background-color: #1a1a1a; + min-height: 100vh; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + padding: 20px; + font-family: 'Montserrat', sans-serif; + box-sizing: border-box; +} + +.phone-frame { + width: 460px; + height: 809px; + background-color: #000; + border-radius: 60px; + padding: 12px; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); + display: flex; + box-sizing: border-box; + position: relative; + border: 4px solid #333; +} + +.mobile-container { + flex: 1; + height: 100%; + background-color: white; + border-radius: 45px; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + position: relative; + box-sizing: border-box; +} + +.mobile-container::-webkit-scrollbar { + width: 4px; +} +.mobile-container::-webkit-scrollbar-thumb { + background: var(--color-fondoBotones); + border-radius: 10px; +} + +.input-line { + border: none !important; + border-bottom: 1px solid #ccc !important; + border-radius: 0 !important; + box-shadow: none !important; + background-color: transparent !important; +} + +.profile-wrapper { + position: relative; + width: 100px; + margin: 0 auto; +} + +.profile-circle { + width: 100px; + height: 100px; + border: 2px dashed var(--color-primario); + border-radius: 50%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + color: var(--color-primario); + overflow: hidden; + padding: 0; + cursor: pointer; + background-color: transparent; +} + +.photo-menu { + position: absolute; + top: 105px; + left: 50%; + transform: translateX(-50%); + background-color: white; + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + display: flex; + flex-direction: column; + z-index: 100; + width: 140px; + overflow: hidden; +} + +.photo-menu button { + background: none; + border: none; + padding: 10px; + text-align: left; + font-size: 14px; + color: var(--color-primario); + cursor: pointer; + transition: background 0.2s; +} + +.photo-menu button:hover { + background-color: var(--color-fondoBotones); +} + +.no-spinners::-webkit-outer-spin-button, +.no-spinners::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.no-spinners[type=number] { + -moz-appearance: textfield; +} + +.my-custom-popup { + border-radius: 30px !important; + font-family: 'Montserrat', sans-serif; +} \ No newline at end of file diff --git a/src/front/pages/AddAutorizado.jsx b/src/front/pages/AddAutorizado.jsx new file mode 100644 index 0000000000..ce7994b5f6 --- /dev/null +++ b/src/front/pages/AddAutorizado.jsx @@ -0,0 +1,196 @@ +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; +import Cloudinary from "../components/Cloudinary.jsx"; +import Swal from 'sweetalert2'; + +export const AddAutorizado = () => { + const { store, actions } = useGlobalReducer(); + const navigate = useNavigate(); + + const [nombre, setNombre] = useState(""); + const [apellidos, setApellidos] = useState(""); + const [telefono, setTelefono] = useState(""); + const [dni, setDni] = useState(""); + const [direccion, setDireccion] = useState(""); + const [parentesco, setParentesco] = useState(""); + const [hijoId, setHijoId] = useState(""); + const [foto, setFoto] = useState(null); + + const [fechaInicio, setFechaInicio] = useState(""); + const [fechaFin, setFechaFin] = useState(""); + const [esPermanente, setEsPermanente] = useState(false); + + const handleTelefonoChange = (e) => { + const value = e.target.value; + const onlyNums = value.replace(/[^0-9]/g, ''); + setTelefono(onlyNums); + }; + + const handleSave = async () => { + const fechasValidas = esPermanente ? true : (fechaInicio && fechaFin); + + if (!nombre || !apellidos || !telefono || !dni || !parentesco || !hijoId || !fechasValidas) { + Swal.fire({ + title: '¡Faltan datos!', + text: 'Rellena todos los campos, incluyendo a quién autorizas recoger.', + icon: 'warning', + confirmButtonColor: 'var(--color-primario)', + customClass: { popup: 'my-custom-popup' } + }); + return; + } + + const nuevoAutorizado = { + nombre, + apellidos, + telefono, + dni, + direccion, + parentesco, + hijoId: parseInt(hijoId), + fotoUrl: foto, + esPermanente, + validoDesde: esPermanente ? "Permanente" : fechaInicio, + validoHasta: esPermanente ? "Indefinido" : fechaFin + }; + + + const success = await actions.addAutorizado(nuevoAutorizado); + + if (success) { + Swal.fire({ + position: 'center', + icon: 'success', + title: '¡Autorizado Guardado!', + text: 'Los datos se han guardado correctamente.', + showConfirmButton: false, + timer: 1500, + customClass: { popup: 'my-custom-popup' } + }).then(() => { + navigate("/menupadre"); + }); + } else { + Swal.fire({ + title: 'Error', + text: 'No se pudo conectar con el servidor para guardar.', + icon: 'error' + }); + } + }; + + return ( +
+
+ +
+ Logo Baby Zzync + + + +
+ +
+ + setFoto(url)} /> + +
+

Persona delegada para recoger a tu hijo/a

+
+ + + + setNombre(e.target.value)} style={{ fontSize: "0.9rem" }} /> + + setApellidos(e.target.value)} style={{ fontSize: "0.9rem" }} /> + + setDni(e.target.value)} style={{ fontSize: "0.9rem" }} /> + + setDireccion(e.target.value)} style={{ fontSize: "0.9rem" }} /> + + + + + + {parentesco !== "Progenitor" && ( + <> +
+ +
+ setEsPermanente(e.target.checked)} style={{ cursor: "pointer", transform: "scale(1.2)" }} /> +
+
+ + {!esPermanente && ( +
+
+ + setFechaInicio(e.target.value)} style={{ fontSize: "0.85rem" }} /> +
+
+ + setFechaFin(e.target.value)} style={{ fontSize: "0.85rem" }} /> +
+
+ )} + + )} + +
+ + Cancelar + + +
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Addhijo.jsx b/src/front/pages/Addhijo.jsx new file mode 100644 index 0000000000..fa1e18a8c2 --- /dev/null +++ b/src/front/pages/Addhijo.jsx @@ -0,0 +1,252 @@ +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import Cloudinary from "../components/Cloudinary.jsx"; +import logoApp from "../assets/Logo Baby Zzync 1 - vers blanca.png"; +import Swal from 'sweetalert2'; + +export const Addhijo = () => { + const { store, actions } = useGlobalReducer(); + const navigate = useNavigate(); + + const [nombre, setNombre] = useState(""); + const [apellidos, setApellidos] = useState(""); + const [edad, setEdad] = useState(0); + const [info, setInfo] = useState(""); + const [foto, setFoto] = useState(""); + + const [hasIntolerancia, setHasIntolerancia] = useState(false); + const [hasAlergia, setHasAlergia] = useState(false); + const [hasAsma, setHasAsma] = useState(false); + + const [intolerancia, setIntolerancia] = useState(""); + const [alergia, setAlergia] = useState(""); + const [nivelAsma, setNivelAsma] = useState(""); + const [tipoSangre, setTipoSangre] = useState(""); + + const [gatea, setGatea] = useState(false); + const [vaAlBano, setVaAlBano] = useState(false); + + const handleSave = async () => { + if (!nombre || !apellidos || edad === 0) { + Swal.fire({ + title: '¡Ups!', + text: 'Por favor, rellena el nombre, apellidos y selecciona una edad.', + icon: 'warning', + confirmButtonText: 'Corregir', + confirmButtonColor: 'var(--color-primario)', + width: '400px', + customClass: { + popup: 'my-custom-popup', + confirmButton: 'rounded-pill px-4 shadow-sm' + } + }); + return; + } + + const localUserData = JSON.parse(localStorage.getItem("user")); + + const hijoData = { + nombre, + apellido: apellidos, + edad, + info, + fotoUrl: foto, + user_id: localUserData?.id, + desarrollo: { + gatea: gatea ? "Sí" : "No", + autonomiaBano: vaAlBano ? "Sí" : "No" + }, + datosMedicos: { + intolerancia: hasIntolerancia ? intolerancia : "Ninguna", + alergia: hasAlergia ? alergia : "Ninguna", + asma: hasAsma ? nivelAsma : "No", + tipoSangre: tipoSangre || "No informado" + } + }; + + const success = await actions.addHijo(hijoData); + + if (success) { + Swal.fire({ + icon: 'success', + title: '¡Niño registrado!', + showConfirmButton: false, + timer: 1500, + width: '400px', + customClass: { popup: 'my-custom-popup' } + }).then(() => { + navigate("/menupadre"); + }); + } else { + Swal.fire({ + icon: 'error', + title: 'Error', + text: 'No se pudo guardar la información en el servidor.', + confirmButtonColor: 'var(--color-primario)', + }); + } + }; + + const selectStyle = { fontSize: "0.85rem", backgroundColor: "#f8f9fa" }; + + return ( +
+
+ + {/* Header */} +
+ + + + logo +
+ +
+ setFoto(url)} /> + +
+ setNombre(e.target.value)} + /> + setApellidos(e.target.value)} + /> + + +
+ + + +
+ +
+

Información Médica Especial

+ +
+
+ Intolerancias +
+ setHasIntolerancia(e.target.checked)} /> +
+
+ {hasIntolerancia && ( + + )} +
+ +
+
+ Alergias +
+ setHasAlergia(e.target.checked)} /> +
+
+ {hasAlergia && ( + + )} +
+ +
+
+ ¿Asmático/a? +
+ setHasAsma(e.target.checked)} /> +
+
+ {hasAsma && ( + + )} +
+ +
+ Grupo Sanguíneo + +
+
+ +
+

Hitos de Desarrollo

+ +
+ ¿Ya gatea? +
+ setGatea(e.target.checked)} /> +
+
+ +
+ ¿Va al baño solo/a? +
+ setVaAlBano(e.target.checked)} /> +
+
+
+ + + {errores.detalles && CAMPO OBLIGATORIO} +
+ +
+ + +
+ +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/DetalleRutinaHijo.jsx b/src/front/pages/DetalleRutinaHijo.jsx new file mode 100644 index 0000000000..5691ac3470 --- /dev/null +++ b/src/front/pages/DetalleRutinaHijo.jsx @@ -0,0 +1,159 @@ +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams, useLocation } from "react-router-dom"; +import { HeaderApp4 } from "../components/HeaderApp4"; +import Swal from 'sweetalert2'; + +const getCategoryIcon = (category) => { + switch (category) { + case 'milk': return ; + case 'sleep': return ; + case 'diaper': return ; + case 'medicine': return ; + case 'exercise': return ; + default: return ; + } +}; + +const getCategoryColor = (category) => { + switch (category) { + case 'milk': return '#4FC3F7'; + case 'sleep': return '#7986CB'; + case 'diaper': return '#FFB74D'; + case 'medicine': return '#E57373'; + case 'exercise': return '#81C784'; + default: return 'var(--color-primario)'; + } +}; + +export const DetalleRutinaHijo = () => { + const { id } = useParams(); + const navigate = useNavigate(); + const location = useLocation(); + const [actividades, setActividades] = useState([]); + + + const { rutina, hijo, esCuidador } = location.state || {}; + const apiUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:3001"; + + useEffect(() => { + const token = localStorage.getItem("token"); + fetch(`${apiUrl}/api/rutinas/${id}/actividades`, { + headers: { "Authorization": `Bearer ${token}` } + }) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data)) { + setActividades(data.sort((a, b) => a.time.localeCompare(b.time))); + } + }); + }, [id, apiUrl]); + + const handleSendRoutine = async () => { + const { value: email } = await Swal.fire({ + title: 'Compartir Rutina', + text: `Introduce el email del cuidador para compartir la rutina de ${hijo?.nombre}`, + input: 'email', + inputPlaceholder: 'email@ejemplo.com', + showCancelButton: true, + confirmButtonText: 'Enviar', + cancelButtonText: 'Cancelar', + confirmButtonColor: 'var(--color-primario)', + cancelButtonColor: '#aaa', + customClass: { + popup: 'rounded-4 shadow', + confirmButton: 'rounded-pill px-4', + cancelButton: 'rounded-pill px-4' + } + }); + + if (email) { + try { + const token = localStorage.getItem("token"); + const response = await fetch(`${apiUrl}/api/rutinas/compartir`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ + email: email, + rutina_id: id, + hijo_id: hijo?.id + }) + }); + + if (response.ok) { + Swal.fire({ + icon: 'success', + title: '¡Rutina compartida!', + text: `Se ha enviado la información a ${email}`, + timer: 2000, + showConfirmButton: false + }); + } else { + const errorData = await response.json(); + throw new Error(errorData.msg || 'No se pudo compartir'); + } + } catch (error) { + Swal.fire({ + icon: 'error', + title: 'Hubo un problema', + text: error.message, + confirmButtonColor: 'var(--color-primario)' + }); + } + } + }; + + return ( +
+ navigate(-1)} + /> + +
+
+

{rutina?.nombre}

+ + Seguimiento para: {hijo?.nombre} + +
+ +
+ {actividades.length === 0 ? ( +

No hay tareas en esta rutina.

+ ) : ( + actividades.map((item) => ( +
+
+
+ {getCategoryIcon(item.category)} +
+
+
{item.text}
+ {item.time} hs - {item.category.toUpperCase()} +
+
+
+ )) + )} +
+ + {!esCuidador && ( +
+ +
+ )} +
+
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/FamiliaRutina.jsx b/src/front/pages/FamiliaRutina.jsx new file mode 100644 index 0000000000..dc1af696d3 --- /dev/null +++ b/src/front/pages/FamiliaRutina.jsx @@ -0,0 +1,224 @@ +import React, { useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; +import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; +import { StaticTimePicker } from '@mui/x-date-pickers/StaticTimePicker'; +import { Dialog, DialogContent, DialogActions, DialogTitle, Button, Typography, TextField, MenuItem, Snackbar, Alert } from '@mui/material'; +import dayjs from 'dayjs'; +import 'dayjs/locale/es'; +import { HeaderApp4 } from "../components/HeaderApp4"; + +const getCategoryIcon = (category) => { + switch (category) { + case 'milk': return ; + case 'sleep': return ; + case 'diaper': return ; + case 'medicine': return ; + case 'exercise': return ; + default: return ; + } +}; + +const getCategoryColor = (category) => { + switch (category) { + case 'milk': return '#4FC3F7'; + case 'sleep': return '#7986CB'; + case 'diaper': return '#FFB74D'; + case 'medicine': return '#E57373'; + case 'exercise': return '#81C784'; + default: return 'var(--color-primario)'; + } +}; + +export const FamiliaRutina = () => { + const navigate = useNavigate(); + const { id } = useParams(); + const apiUrl = import.meta.env.VITE_BACKEND_URL || "http://localhost:3001"; + + const [task, setTask] = useState(""); + const [time, setTime] = useState(dayjs()); + const [category, setCategory] = useState(""); + const [list, setList] = useState([]); + const [snackbar, setSnackbar] = useState({ open: false, message: "", severity: "success" }); + const [openTimeModal, setOpenTimeModal] = useState(false); + const [openEditModal, setOpenEditModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + const [errors, setErrors] = useState({ task: false, category: false }); + + useEffect(() => { + const token = localStorage.getItem("token"); + if (id && token) { + fetch(`${apiUrl}/api/rutinas/${id}/actividades`, { + headers: { "Authorization": `Bearer ${token}` } + }) + .then(res => res.json()) + .then(data => { + if (Array.isArray(data)) { + setList(data.sort((a, b) => a.time.localeCompare(b.time))); + } + }) + .catch(err => console.error(err)); + } + }, [id, apiUrl]); + + const handleAddTask = () => { + const newTaskError = task.trim() === ""; + const newCategoryError = category === ""; + setErrors({ task: newTaskError, category: newCategoryError }); + + if (!newTaskError && !newCategoryError) { + const token = localStorage.getItem("token"); + const nuevaActividad = { + text: task, + time: time.format("HH:mm"), + category: category, + }; + + fetch(`${apiUrl}/api/rutinas/${id}/actividades`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify(nuevaActividad) + }) + .then(res => res.json()) + .then(data => { + setList([...list, data].sort((a, b) => a.time.localeCompare(b.time))); + setTask(""); + setCategory(""); + setSnackbar({ open: true, message: "Actividad añadida", severity: "success" }); + }); + } + }; + + const handleOpenEdit = (item) => { + setEditingItem({ ...item, time: dayjs(`2024-01-01 ${item.time}`) }); + setOpenEditModal(true); + }; + + const handleUpdateTask = () => { + const token = localStorage.getItem("token"); + const updatedData = { + text: editingItem.text, + time: editingItem.time.format("HH:mm"), + category: editingItem.category + }; + + fetch(`${apiUrl}/api/actividades/${editingItem.id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + }, + body: JSON.stringify(updatedData) + }) + .then(res => res.json()) + .then(data => { + setList(list.map(item => item.id === editingItem.id ? data : item).sort((a, b) => a.time.localeCompare(b.time))); + setOpenEditModal(false); + setSnackbar({ open: true, message: "Actualizado", severity: "info" }); + }); + }; + + return ( +
+ navigate("/rutinas", { replace: true })} /> + +
+
+ setTask(e.target.value)} + /> + + + + + + +
+ +
+ {list.length === 0 ? ( +
+ +

Sin actividades aún

+
+ ) : ( + list.map((item) => ( +
+
+
+ {getCategoryIcon(item.category)} +
+
+
{item.text}
+ {item.time} +
+ +
+
+ )) + )} +
+
+ + setOpenEditModal(false)} fullWidth maxWidth="xs"> + Editar Actividad + + setEditingItem({ ...editingItem, text: e.target.value })} /> + + setEditingItem({ ...editingItem, time: val })} + slotProps={{ actionBar: { actions: [] } }} + /> + + + + + + + + + setOpenTimeModal(false)}> + + + setTime(val)} + slotProps={{ actionBar: { actions: [] } }} + /> + + + + + + + + setSnackbar({ ...snackbar, open: false })}> + {snackbar.message} + +
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Home.jsx b/src/front/pages/Home.jsx index 341ed21768..955eccea86 100644 --- a/src/front/pages/Home.jsx +++ b/src/front/pages/Home.jsx @@ -1,52 +1,51 @@ -import React, { useEffect } from "react" -import rigoImageUrl from "../assets/img/rigo-baby.jpg"; -import useGlobalReducer from "../hooks/useGlobalReducer.jsx"; +import React from "react"; +import { Link } from "react-router-dom"; +import { HeaderApp } from "../components/HeaderApp"; export const Home = () => { - - const { store, dispatch } = useGlobalReducer() - - const loadMessage = async () => { - try { - const backendUrl = import.meta.env.VITE_BACKEND_URL - - if (!backendUrl) throw new Error("VITE_BACKEND_URL is not defined in .env file") - - const response = await fetch(backendUrl + "/api/hello") - const data = await response.json() - - if (response.ok) dispatch({ type: "set_hello", payload: data.message }) - - return data - - } catch (error) { - if (error.message) throw new Error( - `Could not fetch the message from the backend. - Please check if the backend is running and the backend port is public.` - ); - } - - } - - useEffect(() => { - loadMessage() - }, []) - - return ( -
-

Hello Rigo!!

-

- Rigo Baby -

-
- {store.message ? ( - {store.message} - ) : ( - - Loading message from the backend (make sure your python 🐍 backend is running)... - - )} -
-
- ); -}; \ No newline at end of file + const btnStyle = { + backgroundColor: "var(--color-fondoBotones)", + borderRadius: "15px", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + border: "none", + padding: "1.5rem", + transition: "all 0.2s ease" + }; + + const iconStyle = { fontSize: "1.5rem", color: "var(--color-primario)" }; + + return ( +
+ + + +
+ + + + + + + + + +
+ +
+ ); +}; \ No newline at end of file diff --git a/src/front/pages/Layout.jsx b/src/front/pages/Layout.jsx index 9bfa31325c..a53d7ec034 100644 --- a/src/front/pages/Layout.jsx +++ b/src/front/pages/Layout.jsx @@ -3,13 +3,22 @@ import ScrollToTop from "../components/ScrollToTop" import { Navbar } from "../components/Navbar" import { Footer } from "../components/Footer" -// Base component that maintains the navbar and footer throughout the page and the scroll to top functionality. export const Layout = () => { return ( - - -