From e443cb1135bd03e3e33b909b4107dc23f4124cd1 Mon Sep 17 00:00:00 2001 From: guisilvagomes Date: Wed, 15 Oct 2025 15:26:53 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20melhorias=20no=20formul=C3=A1rio=20de?= =?UTF-8?q?=20paciente=20e=20avatar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MEDICONNECT 2/package.json | 4 +- MEDICONNECT 2/pnpm-lock.yaml | 435 ++++++++++-------- .../src/components/AgendamentoConsulta.tsx | 148 +++--- .../src/components/DisponibilidadeMedico.tsx | 212 ++++++--- .../components/consultas/ConsultaModal.tsx | 9 +- .../src/components/pacientes/PacienteForm.tsx | 106 ++++- .../components/pacientes/PatientListTable.tsx | 13 +- .../src/pages/AcompanhamentoPaciente.tsx | 16 +- MEDICONNECT 2/src/pages/ListaMedicos.tsx | 10 + MEDICONNECT 2/src/pages/ListaPacientes.tsx | 10 + MEDICONNECT 2/src/pages/PainelMedico.tsx | 260 +++++++---- MEDICONNECT 2/src/pages/PainelSecretaria.tsx | 124 ++--- .../src/services/availabilityService.ts | 59 +++ .../src/services/consultasService.ts | 14 + MEDICONNECT 2/src/services/pacienteService.ts | 3 + 15 files changed, 932 insertions(+), 491 deletions(-) diff --git a/MEDICONNECT 2/package.json b/MEDICONNECT 2/package.json index 0915339f0..ceb8077cb 100644 --- a/MEDICONNECT 2/package.json +++ b/MEDICONNECT 2/package.json @@ -30,7 +30,7 @@ "@types/node": "^24.6.1", "@types/react": "^18.3.5", "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "4.3.2", + "@vitejs/plugin-react": "5.0.4", "autoprefixer": "^10.4.21", "eslint": "^9.9.1", "eslint-plugin-react-hooks": "^5.1.0-rc.0", @@ -40,7 +40,7 @@ "tailwindcss": "^3.4.17", "typescript": "^5.5.3", "typescript-eslint": "^8.3.0", - "vite": "5.4.10" + "vite": "^7.1.10" }, "pnpm": { "overrides": { diff --git a/MEDICONNECT 2/pnpm-lock.yaml b/MEDICONNECT 2/pnpm-lock.yaml index cfe07aa85..af54b9bb2 100644 --- a/MEDICONNECT 2/pnpm-lock.yaml +++ b/MEDICONNECT 2/pnpm-lock.yaml @@ -63,8 +63,8 @@ importers: specifier: ^18.3.0 version: 18.3.7(@types/react@18.3.26) '@vitejs/plugin-react': - specifier: 4.3.2 - version: 4.3.2(vite@5.4.10(@types/node@24.7.2)) + specifier: 5.0.4 + version: 5.0.4(vite@7.1.10(@types/node@24.7.2)(jiti@1.21.7)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -93,8 +93,8 @@ importers: specifier: ^8.3.0 version: 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) vite: - specifier: 5.4.10 - version: 5.4.10(@types/node@24.7.2) + specifier: ^7.1.10 + version: 7.1.10(@types/node@24.7.2)(jiti@1.21.7) packages: @@ -204,23 +204,17 @@ packages: resolution: {integrity: sha512-cxgkB66RQB95H3X27jlnxCRNTmPuSTgmBAq6/4n2Dtv4hsk4yz8FadA1ggmd0uZzvKqWD6CR+WFgTjhDqg7eyw==} engines: {node: '>=18.0.0'} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.10': resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] + '@esbuild/aix-ppc64@0.25.11': + resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.25.10': resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} @@ -228,10 +222,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/android-arm64@0.25.11': + resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + engines: {node: '>=18'} + cpu: [arm64] os: [android] '@esbuild/android-arm@0.25.10': @@ -240,10 +234,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/android-arm@0.25.11': + resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + engines: {node: '>=18'} + cpu: [arm] os: [android] '@esbuild/android-x64@0.25.10': @@ -252,11 +246,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] + '@esbuild/android-x64@0.25.11': + resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.25.10': resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} @@ -264,10 +258,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/darwin-arm64@0.25.11': + resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + engines: {node: '>=18'} + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.25.10': @@ -276,11 +270,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] + '@esbuild/darwin-x64@0.25.11': + resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.25.10': resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} @@ -288,10 +282,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/freebsd-arm64@0.25.11': + resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + engines: {node: '>=18'} + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.25.10': @@ -300,11 +294,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] + '@esbuild/freebsd-x64@0.25.11': + resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.25.10': resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} @@ -312,10 +306,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] + '@esbuild/linux-arm64@0.25.11': + resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + engines: {node: '>=18'} + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.25.10': @@ -324,10 +318,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/linux-arm@0.25.11': + resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + engines: {node: '>=18'} + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.25.10': @@ -336,10 +330,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] + '@esbuild/linux-ia32@0.25.11': + resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + engines: {node: '>=18'} + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.25.10': @@ -348,10 +342,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] + '@esbuild/linux-loong64@0.25.11': + resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + engines: {node: '>=18'} + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.25.10': @@ -360,10 +354,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] + '@esbuild/linux-mips64el@0.25.11': + resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + engines: {node: '>=18'} + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.25.10': @@ -372,10 +366,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] + '@esbuild/linux-ppc64@0.25.11': + resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + engines: {node: '>=18'} + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.25.10': @@ -384,10 +378,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] + '@esbuild/linux-riscv64@0.25.11': + resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + engines: {node: '>=18'} + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.25.10': @@ -396,10 +390,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/linux-s390x@0.25.11': + resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + engines: {node: '>=18'} + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.25.10': @@ -408,16 +402,22 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.25.11': + resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.10': resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/netbsd-arm64@0.25.11': + resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + engines: {node: '>=18'} + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.25.10': @@ -426,16 +426,22 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.11': + resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.10': resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/openbsd-arm64@0.25.11': + resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + engines: {node: '>=18'} + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.25.10': @@ -444,17 +450,23 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.11': + resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.10': resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] + '@esbuild/openharmony-arm64@0.25.11': + resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.25.10': resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} @@ -462,11 +474,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] + '@esbuild/sunos-x64@0.25.11': + resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.25.10': resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} @@ -474,10 +486,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] + '@esbuild/win32-arm64@0.25.11': + resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + engines: {node: '>=18'} + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.25.10': @@ -486,10 +498,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] + '@esbuild/win32-ia32@0.25.11': + resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + engines: {node: '>=18'} + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.25.10': @@ -498,6 +510,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.25.11': + resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -639,6 +657,9 @@ packages: resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} engines: {node: '>=14.0.0'} + '@rolldown/pluginutils@1.0.0-beta.38': + resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -866,11 +887,11 @@ packages: engines: {node: '>=18'} hasBin: true - '@vitejs/plugin-react@4.3.2': - resolution: {integrity: sha512-hieu+o05v4glEBucTcKMK3dlES0OeJlD9YVOAPraVMOInBCwzumaIFiUjr4bHK7NPgnAHgiskUoceKercrN8vg==} - engines: {node: ^14.18.0 || >=16.0.0} + '@vitejs/plugin-react@5.0.4': + resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} + engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 '@vue/compiler-core@3.5.22': resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==} @@ -1338,16 +1359,16 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.25.10: resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} hasBin: true + esbuild@0.25.11: + resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1464,6 +1485,15 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} @@ -2157,8 +2187,8 @@ packages: react: '>=16' react-dom: '>=16' - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} react-router-dom@6.30.1: @@ -2387,6 +2417,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} @@ -2479,22 +2513,27 @@ packages: validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vite@5.4.10: - resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} - engines: {node: ^18.0.0 || >=20.0.0} + vite@7.1.10: + resolution: {integrity: sha512-CmuvUBzVJ/e3HGxhg6cYk88NGgTnBoOo7ogtfJJ0fefUWAxN/WDSUa50o+oVBxuIhO8FoEZW0j2eW7sfjs5EtA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: '@types/node': optional: true + jiti: + optional: true less: optional: true lightningcss: @@ -2509,6 +2548,10 @@ packages: optional: true terser: optional: true + tsx: + optional: true + yaml: + optional: true webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -2718,153 +2761,162 @@ snapshots: '@whatwg-node/promise-helpers': 1.3.2 tslib: 2.8.1 - '@esbuild/aix-ppc64@0.21.5': - optional: true - '@esbuild/aix-ppc64@0.25.10': optional: true - '@esbuild/android-arm64@0.21.5': + '@esbuild/aix-ppc64@0.25.11': optional: true '@esbuild/android-arm64@0.25.10': optional: true - '@esbuild/android-arm@0.21.5': + '@esbuild/android-arm64@0.25.11': optional: true '@esbuild/android-arm@0.25.10': optional: true - '@esbuild/android-x64@0.21.5': + '@esbuild/android-arm@0.25.11': optional: true '@esbuild/android-x64@0.25.10': optional: true - '@esbuild/darwin-arm64@0.21.5': + '@esbuild/android-x64@0.25.11': optional: true '@esbuild/darwin-arm64@0.25.10': optional: true - '@esbuild/darwin-x64@0.21.5': + '@esbuild/darwin-arm64@0.25.11': optional: true '@esbuild/darwin-x64@0.25.10': optional: true - '@esbuild/freebsd-arm64@0.21.5': + '@esbuild/darwin-x64@0.25.11': optional: true '@esbuild/freebsd-arm64@0.25.10': optional: true - '@esbuild/freebsd-x64@0.21.5': + '@esbuild/freebsd-arm64@0.25.11': optional: true '@esbuild/freebsd-x64@0.25.10': optional: true - '@esbuild/linux-arm64@0.21.5': + '@esbuild/freebsd-x64@0.25.11': optional: true '@esbuild/linux-arm64@0.25.10': optional: true - '@esbuild/linux-arm@0.21.5': + '@esbuild/linux-arm64@0.25.11': optional: true '@esbuild/linux-arm@0.25.10': optional: true - '@esbuild/linux-ia32@0.21.5': + '@esbuild/linux-arm@0.25.11': optional: true '@esbuild/linux-ia32@0.25.10': optional: true - '@esbuild/linux-loong64@0.21.5': + '@esbuild/linux-ia32@0.25.11': optional: true '@esbuild/linux-loong64@0.25.10': optional: true - '@esbuild/linux-mips64el@0.21.5': + '@esbuild/linux-loong64@0.25.11': optional: true '@esbuild/linux-mips64el@0.25.10': optional: true - '@esbuild/linux-ppc64@0.21.5': + '@esbuild/linux-mips64el@0.25.11': optional: true '@esbuild/linux-ppc64@0.25.10': optional: true - '@esbuild/linux-riscv64@0.21.5': + '@esbuild/linux-ppc64@0.25.11': optional: true '@esbuild/linux-riscv64@0.25.10': optional: true - '@esbuild/linux-s390x@0.21.5': + '@esbuild/linux-riscv64@0.25.11': optional: true '@esbuild/linux-s390x@0.25.10': optional: true - '@esbuild/linux-x64@0.21.5': + '@esbuild/linux-s390x@0.25.11': optional: true '@esbuild/linux-x64@0.25.10': optional: true + '@esbuild/linux-x64@0.25.11': + optional: true + '@esbuild/netbsd-arm64@0.25.10': optional: true - '@esbuild/netbsd-x64@0.21.5': + '@esbuild/netbsd-arm64@0.25.11': optional: true '@esbuild/netbsd-x64@0.25.10': optional: true + '@esbuild/netbsd-x64@0.25.11': + optional: true + '@esbuild/openbsd-arm64@0.25.10': optional: true - '@esbuild/openbsd-x64@0.21.5': + '@esbuild/openbsd-arm64@0.25.11': optional: true '@esbuild/openbsd-x64@0.25.10': optional: true + '@esbuild/openbsd-x64@0.25.11': + optional: true + '@esbuild/openharmony-arm64@0.25.10': optional: true - '@esbuild/sunos-x64@0.21.5': + '@esbuild/openharmony-arm64@0.25.11': optional: true '@esbuild/sunos-x64@0.25.10': optional: true - '@esbuild/win32-arm64@0.21.5': + '@esbuild/sunos-x64@0.25.11': optional: true '@esbuild/win32-arm64@0.25.10': optional: true - '@esbuild/win32-ia32@0.21.5': + '@esbuild/win32-arm64@0.25.11': optional: true '@esbuild/win32-ia32@0.25.10': optional: true - '@esbuild/win32-x64@0.21.5': + '@esbuild/win32-ia32@0.25.11': optional: true '@esbuild/win32-x64@0.25.10': optional: true + '@esbuild/win32-x64@0.25.11': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@1.21.7))': dependencies: eslint: 9.37.0(jiti@1.21.7) @@ -3086,6 +3138,8 @@ snapshots: '@remix-run/router@1.23.0': {} + '@rolldown/pluginutils@1.0.0-beta.38': {} + '@rollup/pluginutils@5.3.0(rollup@4.52.4)': dependencies: '@types/estree': 1.0.8 @@ -3326,14 +3380,15 @@ snapshots: - rollup - supports-color - '@vitejs/plugin-react@4.3.2(vite@5.4.10(@types/node@24.7.2))': + '@vitejs/plugin-react@5.0.4(vite@7.1.10(@types/node@24.7.2)(jiti@1.21.7))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) + '@rolldown/pluginutils': 1.0.0-beta.38 '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.10(@types/node@24.7.2) + react-refresh: 0.17.0 + vite: 7.1.10(@types/node@24.7.2)(jiti@1.21.7) transitivePeerDependencies: - supports-color @@ -3795,32 +3850,6 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - esbuild@0.25.10: optionalDependencies: '@esbuild/aix-ppc64': 0.25.10 @@ -3850,6 +3879,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.10 '@esbuild/win32-x64': 0.25.10 + esbuild@0.25.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.11 + '@esbuild/android-arm': 0.25.11 + '@esbuild/android-arm64': 0.25.11 + '@esbuild/android-x64': 0.25.11 + '@esbuild/darwin-arm64': 0.25.11 + '@esbuild/darwin-x64': 0.25.11 + '@esbuild/freebsd-arm64': 0.25.11 + '@esbuild/freebsd-x64': 0.25.11 + '@esbuild/linux-arm': 0.25.11 + '@esbuild/linux-arm64': 0.25.11 + '@esbuild/linux-ia32': 0.25.11 + '@esbuild/linux-loong64': 0.25.11 + '@esbuild/linux-mips64el': 0.25.11 + '@esbuild/linux-ppc64': 0.25.11 + '@esbuild/linux-riscv64': 0.25.11 + '@esbuild/linux-s390x': 0.25.11 + '@esbuild/linux-x64': 0.25.11 + '@esbuild/netbsd-arm64': 0.25.11 + '@esbuild/netbsd-x64': 0.25.11 + '@esbuild/openbsd-arm64': 0.25.11 + '@esbuild/openbsd-x64': 0.25.11 + '@esbuild/openharmony-arm64': 0.25.11 + '@esbuild/sunos-x64': 0.25.11 + '@esbuild/win32-arm64': 0.25.11 + '@esbuild/win32-ia32': 0.25.11 + '@esbuild/win32-x64': 0.25.11 + escalade@3.2.0: {} escape-string-regexp@4.0.0: {} @@ -3999,6 +4057,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + fecha@4.2.3: {} file-entry-cache@8.0.0: @@ -4589,7 +4651,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-refresh@0.14.2: {} + react-refresh@0.17.0: {} react-router-dom@6.30.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -4886,6 +4948,11 @@ snapshots: dependencies: any-promise: 1.3.0 + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + tmp-promise@3.0.3: dependencies: tmp: 0.2.5 @@ -4962,14 +5029,18 @@ snapshots: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite@5.4.10(@types/node@24.7.2): + vite@7.1.10(@types/node@24.7.2)(jiti@1.21.7): dependencies: - esbuild: 0.21.5 + esbuild: 0.25.11 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.52.4 + tinyglobby: 0.2.15 optionalDependencies: '@types/node': 24.7.2 fsevents: 2.3.3 + jiti: 1.21.7 webidl-conversions@3.0.1: {} diff --git a/MEDICONNECT 2/src/components/AgendamentoConsulta.tsx b/MEDICONNECT 2/src/components/AgendamentoConsulta.tsx index be25867a1..62c740d8e 100644 --- a/MEDICONNECT 2/src/components/AgendamentoConsulta.tsx +++ b/MEDICONNECT 2/src/components/AgendamentoConsulta.tsx @@ -1,3 +1,4 @@ + import { useState, useEffect, useCallback } from "react"; import { format, @@ -9,28 +10,25 @@ import { isSameMonth, isSameDay, isToday, - parseISO, isBefore, startOfDay, } from "date-fns"; import { ptBR } from "date-fns/locale"; import { - Search, - Star, MapPin, Video, Clock, - CalendarDays, ChevronLeft, ChevronRight, Stethoscope, AlertCircle, CheckCircle2, + Search, } from "lucide-react"; -import { medicoService } from "../services/medicoService"; import { availabilityService } from "../services/availabilityService"; import { exceptionService } from "../services/exceptionService"; import { consultaService } from "../services/consultaService"; +import { medicoService } from "../services/medicoService"; interface Medico { id: string; @@ -81,6 +79,17 @@ const dayOfWeekMap: { [key: number]: keyof Availability } = { }; export default function AgendamentoConsulta() { + // ... + + + // ... outras declarações de hooks e funções ... + + useEffect(() => { + if (selectedMedico) { + loadDoctorAvailability(); + loadDoctorExceptions(); + } + }, [selectedMedico, loadDoctorAvailability, loadDoctorExceptions]); const [medicos, setMedicos] = useState([]); const [filteredMedicos, setFilteredMedicos] = useState([]); const [selectedMedico, setSelectedMedico] = useState(null); @@ -104,16 +113,15 @@ export default function AgendamentoConsulta() { const [bookingError, setBookingError] = useState(""); // Load doctors on mount - useEffect(() => { - loadMedicos(); - }, []); - const loadMedicos = async () => { try { setLoading(true); const data = await medicoService.listarMedicos(); - setMedicos(data); - setFilteredMedicos(data); + // Supondo que data seja ApiResponse + if (data && Array.isArray(data.data)) { + setMedicos(data.data); + setFilteredMedicos(data.data); + } } catch (error) { console.error("Erro ao carregar médicos:", error); } finally { @@ -121,6 +129,10 @@ export default function AgendamentoConsulta() { } }; + useEffect(() => { + loadMedicos(); + }, []); + // Filter doctors based on search and specialty useEffect(() => { let filtered = medicos; @@ -145,20 +157,24 @@ export default function AgendamentoConsulta() { // Get unique specialties const specialties = Array.from(new Set(medicos.map((m) => m.especialidade))); - // Load availability and exceptions when doctor is selected - useEffect(() => { - if (selectedMedico) { - loadDoctorAvailability(); - loadDoctorExceptions(); - } - }, [selectedMedico]); - const loadDoctorAvailability = async () => { + + + + + // ... outras declarações de hooks ... + + + + // ... outras funções e hooks ... + + + const loadDoctorAvailability = useCallback(async () => { if (!selectedMedico) return; try { - const data = await availabilityService.getAvailability(selectedMedico.id); - if (data && data.length > 0) { - const avail = data[0]; + const response = await availabilityService.getAvailability(selectedMedico.id); + if (response && response.success && response.data && response.data.length > 0) { + const avail = response.data[0]; setAvailability({ domingo: avail.domingo || { ativo: false, horarios: [] }, segunda: avail.segunda || { ativo: false, horarios: [] }, @@ -175,57 +191,52 @@ export default function AgendamentoConsulta() { console.error("Erro ao carregar disponibilidade:", error); setAvailability(null); } - }; + }, [selectedMedico]); - const loadDoctorExceptions = async () => { + const loadDoctorExceptions = useCallback(async () => { if (!selectedMedico) return; try { - const data = await exceptionService.listExceptions(selectedMedico.id); - setExceptions(data || []); + const response = await exceptionService.listExceptions({ doctor_id: selectedMedico.id }); + if (response && response.success && response.data) { + setExceptions(response.data as Exception[]); + } else { + setExceptions([]); + } } catch (error) { console.error("Erro ao carregar exceções:", error); setExceptions([]); } - }; + }, [selectedMedico]); // Calculate available slots when date is selected + const calculateAvailableSlots = useCallback(() => { + if (!selectedDate || !availability) return; + const dateStr = format(selectedDate, "yyyy-MM-dd"); + const isBlocked = exceptions.some((exc) => exc.data === dateStr); + if (isBlocked) { + setAvailableSlots([]); + return; + } + const dayOfWeek = selectedDate.getDay(); + const dayKey = dayOfWeekMap[dayOfWeek]; + const daySchedule = availability[dayKey]; + if (!daySchedule || !daySchedule.ativo) { + setAvailableSlots([]); + return; + } + const slots = daySchedule.horarios + .filter((slot) => slot.ativo) + .map((slot) => slot.inicio); + setAvailableSlots(slots); + }, [selectedDate, availability, exceptions]); + useEffect(() => { if (selectedDate && availability && selectedMedico) { calculateAvailableSlots(); } else { setAvailableSlots([]); } - }, [selectedDate, availability, exceptions]); - - const calculateAvailableSlots = () => { - if (!selectedDate || !availability) return; - - // Check if date is an exception (blocked) - const dateStr = format(selectedDate, "yyyy-MM-dd"); - const isBlocked = exceptions.some((exc) => exc.data === dateStr); - - if (isBlocked) { - setAvailableSlots([]); - return; - } - - // Get day of week schedule - const dayOfWeek = selectedDate.getDay(); - const dayKey = dayOfWeekMap[dayOfWeek]; - const daySchedule = availability[dayKey]; - - if (!daySchedule || !daySchedule.ativo) { - setAvailableSlots([]); - return; - } - - // Extract active time slots - const slots = daySchedule.horarios - .filter((slot) => slot.ativo) - .map((slot) => slot.inicio); - - setAvailableSlots(slots); - }; + }, [selectedDate, availability, exceptions, calculateAvailableSlots, selectedMedico]); const isDateBlocked = (date: Date): boolean => { const dateStr = format(date, "yyyy-MM-dd"); @@ -307,20 +318,15 @@ export default function AgendamentoConsulta() { const user = JSON.parse(userStr); - // Create date-time string - const dataHora = `${format( - selectedDate, - "yyyy-MM-dd" - )}T${selectedTime}:00`; + // Removido: dataHora não é usada // Book appointment via API await consultaService.criarConsulta({ - medicoId: selectedMedico.id, - pacienteId: user.id, - dataHora, - tipoConsulta: appointmentType, - motivoConsulta: motivo, - status: "agendada", + paciente_id: user.id, + medico_id: selectedMedico.id, + data_hora: format(selectedDate, "yyyy-MM-dd") + "T" + selectedTime, + tipo_consulta: "primeira_vez", // ou "retorno", "emergencia", "rotina" conforme lógica do sistema + motivo_consulta: motivo, }); setBookingSuccess(true); @@ -334,10 +340,10 @@ export default function AgendamentoConsulta() { setMotivo(""); setBookingSuccess(false); }, 3000); - } catch (error: any) { + } catch (error) { console.error("Erro ao agendar consulta:", error); setBookingError( - error.message || "Erro ao agendar consulta. Tente novamente." + error instanceof Error ? error.message : "Erro ao agendar consulta. Tente novamente." ); setShowConfirmDialog(false); } diff --git a/MEDICONNECT 2/src/components/DisponibilidadeMedico.tsx b/MEDICONNECT 2/src/components/DisponibilidadeMedico.tsx index 921e9d613..ec99fa4b3 100644 --- a/MEDICONNECT 2/src/components/DisponibilidadeMedico.tsx +++ b/MEDICONNECT 2/src/components/DisponibilidadeMedico.tsx @@ -5,18 +5,17 @@ import { Trash2, Save, Copy, - Calendar as CalendarIcon, - AlertCircle, } from "lucide-react"; import toast from "react-hot-toast"; -import { format, addDays, startOfWeek } from "date-fns"; +import { format } from "date-fns"; import { ptBR } from "date-fns/locale"; import availabilityService from "../services/availabilityService"; -import exceptionService from "../services/exceptionService"; +import exceptionService, { DoctorException } from "../services/exceptionService"; import { useAuth } from "../hooks/useAuth"; interface TimeSlot { id: string; + dbId?: string; // ID do banco de dados (se já existir) inicio: string; fim: string; ativo: boolean; @@ -60,44 +59,50 @@ const DisponibilidadeMedico: React.FC = () => { new Date() ); const [blockedDates, setBlockedDates] = useState([]); - const [exceptions, setExceptions] = useState([]); + const [exceptions, setExceptions] = useState([]); // Settings const [consultationDuration, setConsultationDuration] = useState("60"); const [breakTime, setBreakTime] = useState("0"); - useEffect(() => { - if (medicoId) { - loadAvailability(); - loadExceptions(); - } - }, [medicoId]); - - const loadAvailability = async () => { + const loadAvailability = React.useCallback(async () => { try { setLoading(true); - const response = await availabilityService.getAvailability(medicoId); + // Usar listAvailability ao invés de getAvailability para ter os IDs individuais + const response = await availabilityService.listAvailability({ doctor_id: medicoId }); - if (response.success && response.data && response.data.length > 0) { - const availabilityData = response.data[0]; + if (response && response.success && response.data && response.data.length > 0) { const newSchedule: Record = {}; - daysOfWeek.forEach(({ key, label, dbKey }) => { - const dayData = availabilityData[dbKey]; + // Inicializar todos os dias + daysOfWeek.forEach(({ key, label }) => { newSchedule[key] = { day: label, dayOfWeek: key, - enabled: dayData?.ativo || false, - slots: - dayData?.horarios?.map((h: any, index: number) => ({ - id: `${key}-${index}`, - inicio: h.inicio, - fim: h.fim, - ativo: h.ativo !== false, - })) || [], + enabled: false, + slots: [], }; }); + // Agrupar disponibilidades por dia da semana + response.data.forEach((avail) => { + const weekdayKey = daysOfWeek.find(d => d.dbKey === avail.weekday); + if (!weekdayKey) return; + + const dayKey = weekdayKey.key; + if (!newSchedule[dayKey].enabled) { + newSchedule[dayKey].enabled = true; + } + + newSchedule[dayKey].slots.push({ + id: `${dayKey}-${avail.id || Math.random().toString(36).slice(2)}`, + dbId: avail.id, // Armazenar ID do banco + inicio: avail.start_time?.slice(0, 5) || "09:00", + fim: avail.end_time?.slice(0, 5) || "17:00", + ativo: avail.active ?? true, + }); + }); + setSchedule(newSchedule); } else { // Initialize empty schedule @@ -118,22 +123,29 @@ const DisponibilidadeMedico: React.FC = () => { } finally { setLoading(false); } - }; + }, [medicoId]); - const loadExceptions = async () => { + const loadExceptions = React.useCallback(async () => { try { - const response = await exceptionService.listExceptions(medicoId); + const response = await exceptionService.listExceptions({ doctor_id: medicoId }); if (response.success && response.data) { setExceptions(response.data); const blocked = response.data - .filter((exc: any) => exc.tipo === "bloqueio") - .map((exc: any) => new Date(exc.data)); + .filter((exc) => exc.kind === "bloqueio" && exc.date) + .map((exc) => new Date(exc.date!)); setBlockedDates(blocked); } } catch (error) { console.error("Erro ao carregar exceções:", error); } - }; + }, [medicoId]); + + useEffect(() => { + if (medicoId) { + loadAvailability(); + loadExceptions(); + } + }, [medicoId, loadAvailability, loadExceptions]); const toggleDay = (dayKey: number) => { setSchedule((prev) => ({ @@ -169,7 +181,27 @@ const DisponibilidadeMedico: React.FC = () => { } }; - const removeTimeSlot = (dayKey: number, slotId: string) => { + const removeTimeSlot = async (dayKey: number, slotId: string) => { + const slot = schedule[dayKey]?.slots.find(s => s.id === slotId); + + // Se o slot tem um ID do banco, deletar imediatamente + if (slot?.dbId) { + try { + const response = await availabilityService.deleteAvailability(slot.dbId); + if (response.success) { + toast.success("Horário removido com sucesso"); + } else { + toast.error(response.error || "Erro ao remover horário"); + return; + } + } catch (error) { + console.error("Erro ao remover horário:", error); + toast.error("Erro ao remover horário"); + return; + } + } + + // Atualizar o estado local setSchedule((prev) => ({ ...prev, [dayKey]: { @@ -216,36 +248,92 @@ const DisponibilidadeMedico: React.FC = () => { try { setSaving(true); - // Build availability object - const availabilityData: any = { - medico_id: medicoId, + if (!medicoId) { + toast.error("Médico não autenticado"); + return; + } + + const requests: Array> = []; + + const timeToMinutes = (t: string) => { + const [hStr, mStr] = t.split(":"); + const h = Number(hStr || "0"); + const m = Number(mStr || "0"); + return h * 60 + m; }; + // Para cada dia, processar slots daysOfWeek.forEach(({ key, dbKey }) => { const daySchedule = schedule[key]; - availabilityData[dbKey] = { - ativo: daySchedule.enabled, - horarios: daySchedule.slots.map((slot) => ({ - inicio: slot.inicio, - fim: slot.fim, - ativo: slot.ativo, - })), - }; + + if (!daySchedule || !daySchedule.enabled) { + // Se o dia foi desabilitado, deletar todos os slots existentes + daySchedule?.slots.forEach((slot) => { + if (slot.dbId) { + requests.push(availabilityService.deleteAvailability(slot.dbId)); + } + }); + return; + } + + // Processar cada slot do dia + daySchedule.slots.forEach((slot) => { + const inicio = slot.inicio ? (slot.inicio.length === 5 ? `${slot.inicio}:00` : slot.inicio) : "00:00:00"; + const fim = slot.fim ? (slot.fim.length === 5 ? `${slot.fim}:00` : slot.fim) : "00:00:00"; + const minutes = Math.max(1, timeToMinutes(fim.slice(0,5)) - timeToMinutes(inicio.slice(0,5))); + + const payload = { + weekday: dbKey as "segunda" | "terca" | "quarta" | "quinta" | "sexta" | "sabado" | "domingo", + start_time: inicio, + end_time: fim, + slot_minutes: minutes, + appointment_type: "presencial" as const, + active: !!slot.ativo, + }; + + if (slot.dbId) { + // Atualizar slot existente + requests.push(availabilityService.updateAvailability(slot.dbId, payload)); + } else { + // Criar novo slot + requests.push(availabilityService.createAvailability({ + doctor_id: medicoId, + ...payload, + })); + } + }); }); - const response = await availabilityService.createAvailability( - availabilityData - ); - - if (response.success) { - toast.success("Disponibilidade salva com sucesso!"); - loadAvailability(); - } else { - throw new Error(response.message || "Erro ao salvar"); + if (requests.length === 0) { + toast.error("Nenhuma alteração para salvar"); + return; } - } catch (error: any) { + + const results = await Promise.allSettled(requests); + const errors: string[] = []; + let successCount = 0; + results.forEach((r, idx) => { + if (r.status === "fulfilled") { + const val = r.value as { success?: boolean; error?: string; message?: string }; + if (val && val.success) successCount++; + else errors.push(`Item ${idx}: ${val?.error || val?.message || "Erro"}`); + } else { + errors.push(`Item ${idx}: ${r.reason?.message || String(r.reason)}`); + } + }); + + if (errors.length > 0) { + console.error("Erros ao salvar disponibilidades:", errors); + toast.error(`Algumas disponibilidades não foram salvas (${errors.length})`); + } + if (successCount > 0) { + toast.success(`${successCount} alteração(ões) salvas com sucesso!`); + await loadAvailability(); + } + } catch (error) { console.error("Erro ao salvar disponibilidade:", error); - toast.error(error.message || "Erro ao salvar disponibilidade"); + const errorMessage = error instanceof Error ? error.message : "Erro ao salvar disponibilidade"; + toast.error(errorMessage); } finally { setSaving(false); } @@ -263,10 +351,10 @@ const DisponibilidadeMedico: React.FC = () => { if (dateExists) { // Remove block const exception = exceptions.find( - (exc) => format(new Date(exc.data), "yyyy-MM-dd") === dateString + (exc) => exc.date && format(new Date(exc.date), "yyyy-MM-dd") === dateString ); - if (exception) { - await exceptionService.deleteException(exception._id); + if (exception && exception.id) { + await exceptionService.deleteException(exception.id); setBlockedDates( blockedDates.filter((d) => format(d, "yyyy-MM-dd") !== dateString) ); @@ -275,10 +363,10 @@ const DisponibilidadeMedico: React.FC = () => { } else { // Add block const response = await exceptionService.createException({ - medicoId, - data: dateString, - tipo: "bloqueio", - motivo: "Data bloqueada pelo médico", + doctor_id: medicoId, + date: dateString, + kind: "bloqueio", + reason: "Data bloqueada pelo médico", }); if (response.success) { setBlockedDates([...blockedDates, selectedDate]); diff --git a/MEDICONNECT 2/src/components/consultas/ConsultaModal.tsx b/MEDICONNECT 2/src/components/consultas/ConsultaModal.tsx index 270b39dd0..a39d65ab7 100644 --- a/MEDICONNECT 2/src/components/consultas/ConsultaModal.tsx +++ b/MEDICONNECT 2/src/components/consultas/ConsultaModal.tsx @@ -109,12 +109,7 @@ const ConsultaModal: React.FC = ({ setStatus(editing.status || "agendada"); } else { setPacienteId(defaultPacienteId || ""); - // If user is medico, lock to their id if available - if (user?.role === "medico") { - setMedicoId(user.id); - } else { - setMedicoId(defaultMedicoId || ""); - } + setMedicoId(defaultMedicoId || ""); setDataHora(""); setTipo(""); setMotivo(""); @@ -248,7 +243,7 @@ const ConsultaModal: React.FC = ({ className="w-full border rounded px-2 py-2 text-sm" value={medicoId} onChange={(e) => setMedicoId(e.target.value)} - disabled={lockMedico || !!editing || user?.role === "medico"} + disabled={lockMedico || !!editing} > {medicos.map((m) => ( diff --git a/MEDICONNECT 2/src/components/pacientes/PacienteForm.tsx b/MEDICONNECT 2/src/components/pacientes/PacienteForm.tsx index 651e99987..7e704f49e 100644 --- a/MEDICONNECT 2/src/components/pacientes/PacienteForm.tsx +++ b/MEDICONNECT 2/src/components/pacientes/PacienteForm.tsx @@ -1,3 +1,5 @@ +import { useState, useContext } from "react"; +import { AuthContext } from "../../context/AuthContext"; import React from "react"; import type { EnderecoPaciente } from "../../services/pacienteService"; @@ -29,6 +31,7 @@ export interface PacienteFormData { responsavel_cpf?: string; documentos?: { tipo: string; numero: string }[]; endereco: EnderecoPaciente; + avatar_url?: string; } export interface PacienteFormProps { @@ -63,6 +66,57 @@ const PacienteForm: React.FC = ({ onCancel, onSubmit, }) => { + // Avatar upload/remover state + const [avatarEditMode, setAvatarEditMode] = useState(false); + const [avatarFile, setAvatarFile] = useState(null); + const [avatarLoading, setAvatarLoading] = useState(false); + + // Obtem role do usuário autenticado + const { user } = useContext(AuthContext); + const canEditAvatar = ["secretaria", "admin", "gestor"].includes(user?.role); + + // Função para upload do avatar + const handleAvatarUpload = async () => { + if (!avatarFile || !data.id) return; + setAvatarLoading(true); + const formData = new FormData(); + formData.append("file", avatarFile); + await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/${data.id}/avatar`, { + method: "POST", + body: formData, + }); + // Atualiza avatar_url no perfil + const ext = avatarFile.name.split(".").pop(); + const publicUrl = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${data.id}/avatar.${ext}`; + await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/profiles?id=eq.${data.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ avatar_url: publicUrl }), + }); + onChange({ avatar_url: publicUrl }); + setAvatarEditMode(false); + setAvatarFile(null); + setAvatarLoading(false); + }; + + // Função para remover avatar + const handleAvatarRemove = async () => { + if (!data.id) return; + setAvatarLoading(true); + await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/${data.id}/avatar`, { + method: "DELETE", + }); + await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/profiles?id=eq.${data.id}`, { + method: "PATCH", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ avatar_url: null }), + }); + onChange({ avatar_url: undefined }); + setAvatarEditMode(false); + setAvatarFile(null); + setAvatarLoading(false); + }; + return (
= ({ noValidate aria-describedby={cpfError ? "cpf-error" : undefined} > -
+ {/* Bloco do avatar antes do título dos dados pessoais */} +
+
+ {data.avatar_url ? ( + {data.nome} + ) : ( +
+ {data.nome + .split(" ") + .map((n) => n[0]) + .join("") + .toUpperCase() + .slice(0, 2)} +
+ )} + {canEditAvatar && ( + + )} + {avatarEditMode && canEditAvatar && ( +
+ setAvatarFile(e.target.files?.[0] || null)} + className="mb-2" + disabled={avatarLoading} + /> + + + {data.avatar_url && ( + + )} +
+ )} +
+
+ {/* Todos os campos do formulário já estão dentro do abaixo do avatar */} + {/* Os campos do formulário devem continuar aqui, dentro do */}

Dados pessoais diff --git a/MEDICONNECT 2/src/components/pacientes/PatientListTable.tsx b/MEDICONNECT 2/src/components/pacientes/PatientListTable.tsx index 5572a3d8f..6ac128d8a 100644 --- a/MEDICONNECT 2/src/components/pacientes/PatientListTable.tsx +++ b/MEDICONNECT 2/src/components/pacientes/PatientListTable.tsx @@ -14,6 +14,7 @@ export interface PatientListItem { estado?: string; ultimoAtendimento?: string | null; // ISO ou texto humanizado proximoAtendimento?: string | null; + avatar_url?: string; } interface PatientListTableProps { @@ -99,12 +100,20 @@ const PatientListTable: React.FC = ({ {pacientes.map((p) => (
- + {p.avatar_url ? ( + {p.nome} + ) : ( + + )}
{ - const { user, logout } = useAuth(); + const { user, roles = [], logout } = useAuth(); const navigate = useNavigate(); // State @@ -64,8 +64,10 @@ const AcompanhamentoPaciente: React.FC = () => { const pacienteNome = user?.nome || "Paciente"; useEffect(() => { - if (!user) navigate("/paciente"); - }, [user, navigate]); + // Permite acesso se for paciente OU se roles inclui 'paciente' + const isPaciente = user?.role === "paciente" || roles.includes("paciente"); + if (!user || !isPaciente) navigate("/paciente"); + }, [user, roles, navigate]); const fetchConsultas = useCallback(async () => { if (!pacienteId) return; @@ -140,7 +142,9 @@ const AcompanhamentoPaciente: React.FC = () => { } try { - await consultaService.atualizarConsulta(consultaId, { status: "cancelada" }); + await consultaService.atualizarConsulta(consultaId, { + status: "cancelada", + }); toast.success("Consulta cancelada com sucesso"); fetchConsultas(); } catch (error) { @@ -404,13 +408,13 @@ const AcompanhamentoPaciente: React.FC = () => { Entrar na Consulta )} - - + {avatarEditMode && ( + { + e.preventDefault(); + handleAvatarUpload(); + }} + > + setAvatarFile(e.target.files?.[0] || null)} + className="mb-2" + /> + + + {avatarUrl && ( + + )} + + )}

@@ -848,27 +928,15 @@ const PainelMedico: React.FC = () => { {/* Modals */} {modalOpen && ( { setModalOpen(false); setEditing(null); }} - onSave={handleSaveConsulta} - initialData={ - editing - ? { - id: editing.id, - pacienteId: editing.pacienteId, - medicoId: editing.medicoId, - dataHora: editing.dataHora, - status: editing.status, - tipo: editing.tipo, - observacoes: editing.observacoes, - } - : undefined - } - doctorId={medicoId} - doctorName={medicoNome} + onSaved={handleSaveConsulta} + editing={editing} + defaultMedicoId={medicoId} + lockMedico={false} /> )} diff --git a/MEDICONNECT 2/src/pages/PainelSecretaria.tsx b/MEDICONNECT 2/src/pages/PainelSecretaria.tsx index da5808674..898e42cc2 100644 --- a/MEDICONNECT 2/src/pages/PainelSecretaria.tsx +++ b/MEDICONNECT 2/src/pages/PainelSecretaria.tsx @@ -1483,35 +1483,35 @@ const PainelSecretaria = () => { } return ( -

-
-
+
+
+
-

+

Painel da Secretária

-

+

Gerencie pacientes, médicos e consultas

@@ -1519,7 +1519,7 @@ const PainelSecretaria = () => {
-