Compare commits
2 Commits
e1e061c461
...
272f81f44b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
272f81f44b | ||
|
|
e443cb1135 |
@ -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": {
|
||||
|
||||
435
MEDICONNECT 2/pnpm-lock.yaml
generated
435
MEDICONNECT 2/pnpm-lock.yaml
generated
@ -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: {}
|
||||
|
||||
|
||||
@ -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<Medico[]>([]);
|
||||
const [filteredMedicos, setFilteredMedicos] = useState<Medico[]>([]);
|
||||
const [selectedMedico, setSelectedMedico] = useState<Medico | null>(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<MedicoListResponse>
|
||||
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);
|
||||
}
|
||||
|
||||
@ -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<Date[]>([]);
|
||||
const [exceptions, setExceptions] = useState<any[]>([]);
|
||||
const [exceptions, setExceptions] = useState<DoctorException[]>([]);
|
||||
|
||||
// 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<number, DaySchedule> = {};
|
||||
|
||||
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<Promise<unknown>> = [];
|
||||
|
||||
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]);
|
||||
|
||||
@ -109,12 +109,7 @@ const ConsultaModal: React.FC<ConsultaModalProps> = ({
|
||||
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<ConsultaModalProps> = ({
|
||||
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}
|
||||
>
|
||||
<option value="">Selecione...</option>
|
||||
{medicos.map((m) => (
|
||||
|
||||
@ -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<PacienteFormProps> = ({
|
||||
onCancel,
|
||||
onSubmit,
|
||||
}) => {
|
||||
// Avatar upload/remover state
|
||||
const [avatarEditMode, setAvatarEditMode] = useState(false);
|
||||
const [avatarFile, setAvatarFile] = useState<File | null>(null);
|
||||
const [avatarLoading, setAvatarLoading] = useState(false);
|
||||
|
||||
// Obtem role do usuário autenticado
|
||||
const auth = useContext(AuthContext);
|
||||
const canEditAvatar = ["secretaria", "admin", "gestor"].includes(auth?.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 (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
@ -70,7 +124,57 @@ const PacienteForm: React.FC<PacienteFormProps> = ({
|
||||
noValidate
|
||||
aria-describedby={cpfError ? "cpf-error" : undefined}
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* Bloco do avatar antes do título dos dados pessoais */}
|
||||
<div className="flex items-center gap-4 mb-6">
|
||||
<div className="relative group">
|
||||
{data.avatar_url ? (
|
||||
<img
|
||||
src={data.avatar_url}
|
||||
alt={data.nome}
|
||||
className="h-16 w-16 rounded-full object-cover border shadow"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-16 w-16 rounded-full bg-gradient-to-br from-blue-700 to-blue-400 flex items-center justify-center text-white font-semibold text-lg shadow">
|
||||
{data.nome
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2)}
|
||||
</div>
|
||||
)}
|
||||
{canEditAvatar && (
|
||||
<button
|
||||
type="button"
|
||||
className="absolute bottom-0 right-0 bg-white rounded-full p-1 border shadow group-hover:bg-blue-100 transition"
|
||||
title="Editar avatar"
|
||||
onClick={() => setAvatarEditMode(true)}
|
||||
style={{ lineHeight: 0 }}
|
||||
disabled={avatarLoading}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="text-blue-600" width="16" height="16" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15.232 5.232l3.536 3.536M9 13l6.586-6.586a2 2 0 112.828 2.828L11.828 15.828a2 2 0 01-2.828 0L9 13zm0 0V17h4" /></svg>
|
||||
</button>
|
||||
)}
|
||||
{avatarEditMode && canEditAvatar && (
|
||||
<div className="absolute top-0 left-20 bg-white p-2 rounded shadow z-10 flex flex-col items-center">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
onChange={e => setAvatarFile(e.target.files?.[0] || null)}
|
||||
className="mb-2"
|
||||
disabled={avatarLoading}
|
||||
/>
|
||||
<button type="button" className="text-xs bg-blue-600 text-white px-2 py-1 rounded" onClick={handleAvatarUpload} disabled={avatarLoading}>Salvar</button>
|
||||
<button type="button" className="text-xs ml-2" onClick={() => setAvatarEditMode(false)} disabled={avatarLoading}>Cancelar</button>
|
||||
{data.avatar_url && (
|
||||
<button type="button" className="text-xs text-red-600 underline mt-2" onClick={handleAvatarRemove} disabled={avatarLoading}>Remover</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Todos os campos do formulário já estão dentro do <form> abaixo do avatar */}
|
||||
{/* Os campos do formulário devem continuar aqui, dentro do <form> */}
|
||||
<div className="md:col-span-2">
|
||||
<h4 className="text-xs font-semibold uppercase tracking-wide text-green-600">
|
||||
Dados pessoais
|
||||
|
||||
@ -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<PatientListTableProps> = ({
|
||||
{pacientes.map((p) => (
|
||||
<tr
|
||||
key={p.id}
|
||||
className="odd:bg-white even:bg-gray-50/60 dark:even:bg-gray-800/50 hover:bg-blue-50/50 dark:hover:bg-gray-800 transition-colors"
|
||||
className="bg-white dark:bg-gray-900 hover:bg-blue-50/50 dark:hover:bg-gray-800 transition-colors"
|
||||
role="row"
|
||||
>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-start gap-3">
|
||||
<AvatarInitials name={p.nome} size={40} />
|
||||
{p.avatar_url ? (
|
||||
<img
|
||||
src={p.avatar_url}
|
||||
alt={p.nome}
|
||||
className="h-10 w-10 rounded-full object-cover border"
|
||||
/>
|
||||
) : (
|
||||
<AvatarInitials name={p.nome} size={40} />
|
||||
)}
|
||||
<div>
|
||||
<div
|
||||
className="text-sm font-semibold text-gray-900 dark:text-gray-100 cursor-pointer hover:underline"
|
||||
|
||||
@ -50,7 +50,7 @@ interface Medico {
|
||||
}
|
||||
|
||||
const AcompanhamentoPaciente: React.FC = () => {
|
||||
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) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import AvatarInitials from "../components/AvatarInitials";
|
||||
import { Stethoscope, Mail, Phone, AlertTriangle } from "lucide-react";
|
||||
import medicoService, { MedicoDetalhado } from "../services/medicoService";
|
||||
|
||||
@ -73,6 +74,15 @@ const ListaMedicos: React.FC = () => {
|
||||
tabIndex={0}
|
||||
>
|
||||
<header className="flex items-center gap-2">
|
||||
{medico.avatar_url ? (
|
||||
<img
|
||||
src={medico.avatar_url}
|
||||
alt={medico.nome}
|
||||
className="h-10 w-10 rounded-full object-cover border"
|
||||
/>
|
||||
) : (
|
||||
<AvatarInitials name={medico.nome} size={40} />
|
||||
)}
|
||||
<Stethoscope className="w-5 h-5 text-indigo-600" />
|
||||
<h3 className="font-semibold text-lg text-gray-900">
|
||||
{medico.nome}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import AvatarInitials from "../components/AvatarInitials";
|
||||
// Funções utilitárias para formatação
|
||||
function formatCPF(cpf?: string) {
|
||||
if (!cpf) return "Não informado";
|
||||
@ -85,6 +86,15 @@ const ListaPacientes: React.FC = () => {
|
||||
tabIndex={0}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{paciente.avatar_url ? (
|
||||
<img
|
||||
src={paciente.avatar_url}
|
||||
alt={paciente.nome}
|
||||
className="h-10 w-10 rounded-full object-cover border"
|
||||
/>
|
||||
) : (
|
||||
<AvatarInitials name={paciente.nome} size={40} />
|
||||
)}
|
||||
<Users className="w-5 h-5 text-blue-600" />
|
||||
<span className="font-semibold text-lg">{paciente.nome}</span>
|
||||
</div>
|
||||
|
||||
@ -17,12 +17,13 @@ import {
|
||||
Plus,
|
||||
Edit,
|
||||
Trash2,
|
||||
Pencil,
|
||||
} from "lucide-react";
|
||||
import toast from "react-hot-toast";
|
||||
import { format } from "date-fns";
|
||||
import { ptBR } from "date-fns/locale";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Consulta as ServiceConsulta } from "../services/consultasService";
|
||||
import consultasService, { Consulta as ServiceConsulta } from "../services/consultasService";
|
||||
import { listPatients } from "../services/pacienteService";
|
||||
import { useAuth } from "../hooks/useAuth";
|
||||
import relatorioService, {
|
||||
@ -30,8 +31,6 @@ import relatorioService, {
|
||||
} from "../services/relatorioService";
|
||||
import DisponibilidadeMedico from "../components/DisponibilidadeMedico";
|
||||
import ConsultaModal from "../components/consultas/ConsultaModal";
|
||||
import AvailabilityManager from "../components/agenda/AvailabilityManager";
|
||||
import ExceptionsManager from "../components/agenda/ExceptionsManager";
|
||||
|
||||
interface ConsultaUI {
|
||||
id: string;
|
||||
@ -45,14 +44,7 @@ interface ConsultaUI {
|
||||
observacoes?: string;
|
||||
}
|
||||
|
||||
interface Paciente {
|
||||
_id: string;
|
||||
nome: string;
|
||||
telefone: string;
|
||||
email: string;
|
||||
convenio: string;
|
||||
observacoes: string;
|
||||
}
|
||||
|
||||
|
||||
const PainelMedico: React.FC = () => {
|
||||
const { user, roles, logout } = useAuth();
|
||||
@ -66,6 +58,70 @@ const PainelMedico: React.FC = () => {
|
||||
roles.includes("admin"));
|
||||
const medicoId = temAcessoMedico ? user.id : "";
|
||||
const medicoNome = user?.nome || "Médico";
|
||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(user?.avatar_url || null);
|
||||
const [avatarEditMode, setAvatarEditMode] = useState(false);
|
||||
const [avatarFile, setAvatarFile] = useState<File | null>(null);
|
||||
|
||||
// Função para buscar avatar público
|
||||
const fetchAvatarUrl = useCallback(() => {
|
||||
if (!user?.id) return;
|
||||
// Tenta jpg, png, webp
|
||||
const base = `https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/public/avatars/${user.id}/avatar`;
|
||||
const tryExts = async () => {
|
||||
for (const ext of ["jpg", "png", "webp"]) {
|
||||
const url = `${base}.${ext}`;
|
||||
try {
|
||||
const res = await fetch(url, { method: "HEAD" });
|
||||
if (res.ok) {
|
||||
setAvatarUrl(url);
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
setAvatarUrl(null);
|
||||
};
|
||||
tryExts();
|
||||
}, [user?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAvatarUrl();
|
||||
}, [fetchAvatarUrl]);
|
||||
|
||||
// Upload avatar
|
||||
const handleAvatarUpload = async () => {
|
||||
if (!avatarFile || !user?.id) return;
|
||||
const formData = new FormData();
|
||||
formData.append("file", avatarFile);
|
||||
await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/${user.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/${user.id}/avatar.${ext}`;
|
||||
await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/profiles?id=eq.${user.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ avatar_url: publicUrl }),
|
||||
});
|
||||
setAvatarEditMode(false);
|
||||
setAvatarFile(null);
|
||||
setAvatarUrl(publicUrl);
|
||||
};
|
||||
|
||||
// Remover avatar
|
||||
const handleAvatarRemove = async () => {
|
||||
if (!user?.id) return;
|
||||
await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/storage/v1/object/avatars/${user.id}/avatar`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/profiles?id=eq.${user.id}`, {
|
||||
method: "PATCH",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ avatar_url: null }),
|
||||
});
|
||||
setAvatarUrl(null);
|
||||
};
|
||||
|
||||
// State
|
||||
const [activeTab, setActiveTab] = useState("dashboard");
|
||||
@ -99,71 +155,56 @@ const PainelMedico: React.FC = () => {
|
||||
}, [medicoId, navigate]);
|
||||
|
||||
const fetchConsultas = useCallback(async () => {
|
||||
if (!medicoId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const raw = localStorage.getItem("consultas_local");
|
||||
let lista: ServiceConsulta[] = [];
|
||||
if (raw) {
|
||||
try {
|
||||
lista = JSON.parse(raw);
|
||||
} catch {
|
||||
lista = [];
|
||||
}
|
||||
let resp;
|
||||
if (user?.role === "admin" || roles.includes("admin")) {
|
||||
// Admin: busca todas as consultas do sistema
|
||||
resp = await consultasService.listarTodas();
|
||||
} else {
|
||||
// Médico comum: busca todas as consultas do próprio médico
|
||||
if (!medicoId) return;
|
||||
resp = await consultasService.listarPorMedico(medicoId);
|
||||
}
|
||||
let filtradas = lista.filter((c) => c.medicoId === medicoId);
|
||||
const hoje = new Date();
|
||||
if (filtroData === "hoje") {
|
||||
const dStr = format(hoje, "yyyy-MM-dd");
|
||||
filtradas = filtradas.filter((c) => c.dataHora.startsWith(dStr));
|
||||
} else if (filtroData === "amanha") {
|
||||
const amanha = new Date(hoje);
|
||||
amanha.setDate(hoje.getDate() + 1);
|
||||
const dStr = format(amanha, "yyyy-MM-dd");
|
||||
filtradas = filtradas.filter((c) => c.dataHora.startsWith(dStr));
|
||||
} else if (filtroData === "semana") {
|
||||
const start = new Date(hoje);
|
||||
start.setDate(hoje.getDate() - hoje.getDay());
|
||||
const end = new Date(start);
|
||||
end.setDate(start.getDate() + 6);
|
||||
filtradas = filtradas.filter((c) => {
|
||||
const d = new Date(c.dataHora);
|
||||
return d >= start && d <= end;
|
||||
});
|
||||
if (resp && resp.success && resp.data) {
|
||||
// Buscar nomes dos pacientes usando getPatientById
|
||||
const { getPatientById } = await import("../services/pacienteService");
|
||||
const consultasComNomes = await Promise.all(
|
||||
resp.data.map(async (c) => {
|
||||
let pacienteNome = "Paciente Desconhecido";
|
||||
try {
|
||||
const pacienteResp = await getPatientById(c.pacienteId);
|
||||
if (pacienteResp.success && pacienteResp.data) {
|
||||
pacienteNome = pacienteResp.data.nome;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar nome do paciente:", error);
|
||||
}
|
||||
return {
|
||||
id: c.id,
|
||||
pacienteId: c.pacienteId,
|
||||
medicoId: c.medicoId,
|
||||
pacienteNome,
|
||||
medicoNome: medicoNome,
|
||||
dataHora: c.dataHora,
|
||||
status: c.status,
|
||||
tipo: c.tipo,
|
||||
observacoes: c.observacoes,
|
||||
};
|
||||
})
|
||||
);
|
||||
setConsultas(consultasComNomes);
|
||||
} else {
|
||||
setConsultas([]);
|
||||
}
|
||||
const pacientesResponse = await listPatients({ per_page: 200 }).catch(
|
||||
() => ({ data: [], total: 0, page: 1, per_page: 0 })
|
||||
);
|
||||
const pacMap: Record<string, Paciente> = {};
|
||||
const pacientesLista =
|
||||
"data" in pacientesResponse ? pacientesResponse.data : [];
|
||||
pacientesLista.forEach((p) => {
|
||||
pacMap[p.id] = {
|
||||
_id: p.id,
|
||||
nome: p.nome,
|
||||
telefone: p.telefone || "",
|
||||
email: p.email || "",
|
||||
convenio: p.convenio || "",
|
||||
observacoes: p.observacoes || "",
|
||||
};
|
||||
});
|
||||
setConsultas(
|
||||
filtradas.map((c) => ({
|
||||
id: c.id,
|
||||
pacienteId: c.pacienteId,
|
||||
medicoId: c.medicoId,
|
||||
pacienteNome: pacMap[c.pacienteId]?.nome || c.pacienteId,
|
||||
medicoNome: medicoNome,
|
||||
dataHora: c.dataHora,
|
||||
status: c.status,
|
||||
tipo: c.tipo,
|
||||
observacoes: c.observacoes,
|
||||
}))
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Erro ao buscar consultas:", error);
|
||||
toast.error("Erro ao carregar consultas");
|
||||
setConsultas([]);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [medicoId, filtroData, medicoNome]);
|
||||
}, [user, roles, medicoId, medicoNome]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchConsultas();
|
||||
@ -334,9 +375,8 @@ const PainelMedico: React.FC = () => {
|
||||
};
|
||||
|
||||
// Stats
|
||||
const consultasHoje = consultas.filter((c) =>
|
||||
c.dataHora.startsWith(format(new Date(), "yyyy-MM-dd"))
|
||||
);
|
||||
// Exibe todas as consultas do médico logado, sem filtro extra
|
||||
const consultasHoje = consultas;
|
||||
const consultasConfirmadas = consultas.filter(
|
||||
(c) =>
|
||||
c.status.toLowerCase() === "confirmada" ||
|
||||
@ -363,13 +403,53 @@ const PainelMedico: React.FC = () => {
|
||||
{/* Doctor Profile */}
|
||||
<div className="p-6 border-b border-gray-200 dark:border-slate-700">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-12 w-12 rounded-full bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg">
|
||||
{medicoNome
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2)}
|
||||
<div className="relative group">
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="h-14 w-14 rounded-full object-cover border shadow"
|
||||
/>
|
||||
) : (
|
||||
<div className="h-14 w-14 rounded-full bg-gradient-to-br from-indigo-500 to-indigo-600 flex items-center justify-center text-white font-semibold text-lg shadow">
|
||||
{medicoNome
|
||||
.split(" ")
|
||||
.map((n) => n[0])
|
||||
.join("")
|
||||
.toUpperCase()
|
||||
.slice(0, 2)}
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="absolute bottom-0 right-0 bg-white rounded-full p-1 border shadow group-hover:bg-indigo-100 transition"
|
||||
title="Editar avatar"
|
||||
onClick={() => setAvatarEditMode(true)}
|
||||
style={{ lineHeight: 0 }}
|
||||
>
|
||||
<Pencil size={16} className="text-indigo-600" />
|
||||
</button>
|
||||
{avatarEditMode && (
|
||||
<form
|
||||
className="absolute top-0 left-16 bg-white p-2 rounded shadow z-10 flex flex-col items-center"
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
handleAvatarUpload();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/webp"
|
||||
onChange={e => setAvatarFile(e.target.files?.[0] || null)}
|
||||
className="mb-2"
|
||||
/>
|
||||
<button type="submit" className="text-xs bg-indigo-600 text-white px-2 py-1 rounded">Salvar</button>
|
||||
<button type="button" className="text-xs ml-2" onClick={() => setAvatarEditMode(false)}>Cancelar</button>
|
||||
{avatarUrl && (
|
||||
<button type="button" className="text-xs text-red-600 underline mt-2" onClick={handleAvatarRemove}>Remover</button>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium text-gray-900 dark:text-white">
|
||||
@ -848,27 +928,15 @@ const PainelMedico: React.FC = () => {
|
||||
{/* Modals */}
|
||||
{modalOpen && (
|
||||
<ConsultaModal
|
||||
open={modalOpen}
|
||||
isOpen={modalOpen}
|
||||
onClose={() => {
|
||||
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}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -1483,35 +1483,35 @@ const PainelSecretaria = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
<header className="bg-white/90 backdrop-blur supports-[backdrop-filter]:bg-white/70 shadow-sm border-b">
|
||||
<div className="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-4 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 high-contrast:bg-black">
|
||||
<header className="bg-white/90 dark:bg-gray-800/90 high-contrast:bg-black shadow-sm border-b dark:border-gray-700 high-contrast:border-gray-700">
|
||||
<div className="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-4 flex flex-col gap-4 md:flex-row md:items-center md:justify-between high-contrast:text-white">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white high-contrast:text-black high-contrast:font-extrabold">
|
||||
Painel da Secretária
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
<p className="text-gray-600 dark:text-white high-contrast:text-black high-contrast:font-bold">
|
||||
Gerencie pacientes, médicos e consultas
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<button
|
||||
onClick={openCreatePacienteModal}
|
||||
className="bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-green-500"
|
||||
className="bg-green-600 text-white high-contrast:bg-black high-contrast:text-white px-4 py-2 rounded-lg hover:bg-green-700 transition-colors flex items-center focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-green-500"
|
||||
>
|
||||
<Plus className="w-5 h-5 mr-2" />
|
||||
Novo Paciente
|
||||
</button>
|
||||
<button
|
||||
onClick={openCreateMedicoModal}
|
||||
className="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
||||
className="bg-blue-600 text-white high-contrast:bg-black high-contrast:text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition-colors flex items-center focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
|
||||
>
|
||||
<UserPlus className="w-5 h-5 mr-2" />
|
||||
Novo Médico
|
||||
</button>
|
||||
<button
|
||||
onClick={handleLogout}
|
||||
className="bg-red-600 text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-red-500"
|
||||
className="bg-red-600 text-white high-contrast:bg-black high-contrast:text-white px-4 py-2 rounded-lg hover:bg-red-700 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-red-500"
|
||||
>
|
||||
Sair
|
||||
</button>
|
||||
@ -1519,7 +1519,7 @@ const PainelSecretaria = () => {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav className="bg-white/90 backdrop-blur supports-[backdrop-filter]:bg-white/70 border-b">
|
||||
<nav className="bg-white/90 dark:bg-gray-800/90 high-contrast:bg-black border-b dark:border-gray-700 high-contrast:border-gray-700">
|
||||
<div className="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex space-x-2 sm:space-x-4 overflow-x-auto">
|
||||
{[
|
||||
@ -1542,8 +1542,8 @@ const PainelSecretaria = () => {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`flex items-center px-3 py-4 border-b-2 font-medium text-sm whitespace-nowrap focus:outline-none focus-visible:ring-2 focus-visible:ring-green-500/50 ${
|
||||
selected
|
||||
? "border-green-500 text-green-600"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
? "border-green-500 text-green-600 high-contrast:text-white"
|
||||
: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 high-contrast:text-white"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-5 h-5 mr-2" />
|
||||
@ -1555,10 +1555,10 @@ const PainelSecretaria = () => {
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
<main className="max-w-[1600px] mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{activeTab === "dashboard" && (
|
||||
<section className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow border dark:border-gray-700">
|
||||
<p className="text-sm font-medium text-gray-600">
|
||||
Pacientes cadastrados
|
||||
</p>
|
||||
@ -1566,7 +1566,7 @@ const PainelSecretaria = () => {
|
||||
{pacientes.length}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow border dark:border-gray-700">
|
||||
<p className="text-sm font-medium text-gray-600">
|
||||
Médicos ativos
|
||||
</p>
|
||||
@ -1574,7 +1574,7 @@ const PainelSecretaria = () => {
|
||||
{medicos.length}
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow border dark:border-gray-700">
|
||||
<p className="text-sm font-medium text-gray-600">
|
||||
Consultas programadas
|
||||
</p>
|
||||
@ -1587,8 +1587,8 @@ const PainelSecretaria = () => {
|
||||
|
||||
{activeTab === "pacientes" && (
|
||||
<section className="space-y-6">
|
||||
<div className="bg-white rounded-xl shadow border border-gray-200">
|
||||
<div className="p-6 border-b space-y-4">
|
||||
<div className="bg-white dark:bg-gray-800 rounded-xl shadow border border-gray-200 dark:border-gray-700">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 space-y-4">
|
||||
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
@ -1597,7 +1597,7 @@ const PainelSecretaria = () => {
|
||||
placeholder="Buscar pacientes por nome ou email..."
|
||||
value={searchTerm}
|
||||
onChange={(event) => setSearchTerm(event.target.value)}
|
||||
className="pl-10 pr-4 py-2 w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
className="pl-10 pr-4 py-2 w-full border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col md:flex-row gap-2">
|
||||
@ -1606,7 +1606,7 @@ const PainelSecretaria = () => {
|
||||
placeholder="Buscar paciente por ID"
|
||||
value={searchId}
|
||||
onChange={(event) => setSearchId(event.target.value)}
|
||||
className="border px-3 py-2 rounded-lg w/full md:w-64 focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
className="border px-3 py-2 rounded-lg w/full md:w-64 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 border-gray-300 dark:border-gray-700 focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
/>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
@ -1624,7 +1624,7 @@ const PainelSecretaria = () => {
|
||||
setFilterVip(false);
|
||||
void carregarPacientes();
|
||||
}}
|
||||
className="px-4 py-2 border border-gray-300 rounded-lg text-gray-700 hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-300"
|
||||
className="px-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg text-gray-700 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-800 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-gray-300 dark:focus-visible:ring-gray-700"
|
||||
>
|
||||
Limpar
|
||||
</button>
|
||||
@ -1668,7 +1668,7 @@ const PainelSecretaria = () => {
|
||||
onChange={(event) =>
|
||||
setSelectedConvenio(event.target.value)
|
||||
}
|
||||
className="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
className="px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-green-600 focus:border-green-600/40 transition-colors"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
{conveniosDisponiveis.map((item) => (
|
||||
@ -1724,7 +1724,7 @@ const PainelSecretaria = () => {
|
||||
|
||||
{activeTab === "medicos" && (
|
||||
<section className="space-y-6">
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="bg-white dark:bg-gray-800 high-contrast:bg-black rounded-lg shadow border dark:border-gray-700 high-contrast:border-gray-700">
|
||||
<div className="p-6 border-b flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex-1 relative">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-gray-400" />
|
||||
@ -1739,8 +1739,8 @@ const PainelSecretaria = () => {
|
||||
</div>
|
||||
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50 dark:bg-gray-900 high-contrast:bg-black">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Médico
|
||||
@ -1756,28 +1756,28 @@ const PainelSecretaria = () => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
<tbody className="bg-white dark:bg-gray-800 high-contrast:bg-black divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
{medicosFiltrados.map((medico) => (
|
||||
<tr
|
||||
key={medico.id}
|
||||
className="odd:bg-white even:bg-gray-50 hover:bg-gray-100"
|
||||
className="bg-white dark:bg-gray-800 high-contrast:bg-black hover:bg-gray-100 dark:hover:bg-gray-700 high-contrast:hover:bg-gray-900"
|
||||
>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
<td className="px-6 py-4 high-contrast:text-white">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 high-contrast:text-white">
|
||||
Dr(a). {medico.nome || "Sem nome"}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<div className="text-sm text-gray-400 dark:text-gray-300 high-contrast:text-white">
|
||||
CRM: {medico.crm || "Não informado"}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4 text-sm text-gray-500">
|
||||
<td className="px-6 py-4 text-sm text-gray-400 dark:text-gray-300 high-contrast:text-white">
|
||||
{medico.especialidade || "Não informado"}
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="text-sm text-gray-900">
|
||||
<td className="px-6 py-4 high-contrast:text-white">
|
||||
<div className="text-sm text-gray-900 dark:text-gray-100 high-contrast:text-white">
|
||||
{formatEmail(medico.email)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<div className="text-sm text-gray-400 dark:text-gray-300 high-contrast:text-white">
|
||||
{medico.telefone || "Telefone não informado"}
|
||||
</div>
|
||||
</td>
|
||||
@ -1815,8 +1815,8 @@ const PainelSecretaria = () => {
|
||||
)}
|
||||
|
||||
{activeTab === "consultas" && (
|
||||
<section className="bg-white rounded-lg shadow">
|
||||
<div className="p-6 border-b flex flex-col gap-4">
|
||||
<section className="bg-white dark:bg-gray-800 high-contrast:bg-black rounded-lg shadow border border-gray-200 dark:border-gray-700 high-contrast:border-gray-700">
|
||||
<div className="p-6 border-b border-gray-200 dark:border-gray-700 flex flex-col gap-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
Agendamentos
|
||||
@ -1849,7 +1849,7 @@ const PainelSecretaria = () => {
|
||||
placeholder="Busca rápida (paciente, médico ou tipo)"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
className="flex-1 px-3 py-2 border border-gray-300 dark:border-gray-700 rounded-lg bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-3">
|
||||
@ -1861,7 +1861,7 @@ const PainelSecretaria = () => {
|
||||
type="date"
|
||||
value={consultaFiltroDataDe}
|
||||
onChange={(e) => setConsultaFiltroDataDe(e.target.value)}
|
||||
className="w-full border rounded px-2 py-1 text-sm"
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -1872,7 +1872,7 @@ const PainelSecretaria = () => {
|
||||
type="date"
|
||||
value={consultaFiltroDataAte}
|
||||
onChange={(e) => setConsultaFiltroDataAte(e.target.value)}
|
||||
className="w-full border rounded px-2 py-1 text-sm"
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@ -1882,7 +1882,7 @@ const PainelSecretaria = () => {
|
||||
<select
|
||||
value={consultaFiltroStatus}
|
||||
onChange={(e) => setConsultaFiltroStatus(e.target.value)}
|
||||
className="w-full border rounded px-2 py-1 text-sm"
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||
>
|
||||
<option value="">Todos</option>
|
||||
<option value="agendada">Agendada</option>
|
||||
@ -1898,7 +1898,7 @@ const PainelSecretaria = () => {
|
||||
<input
|
||||
value={consultaFiltroPaciente}
|
||||
onChange={(e) => setConsultaFiltroPaciente(e.target.value)}
|
||||
className="w-full border rounded px-2 py-1 text-sm"
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||
placeholder="Filtrar paciente"
|
||||
/>
|
||||
</div>
|
||||
@ -1909,7 +1909,7 @@ const PainelSecretaria = () => {
|
||||
<input
|
||||
value={consultaFiltroMedico}
|
||||
onChange={(e) => setConsultaFiltroMedico(e.target.value)}
|
||||
className="w-full border rounded px-2 py-1 text-sm"
|
||||
className="w-full border border-gray-300 dark:border-gray-700 rounded px-2 py-1 text-sm bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
||||
placeholder="Filtrar médico"
|
||||
/>
|
||||
</div>
|
||||
@ -1927,8 +1927,8 @@ const PainelSecretaria = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50 dark:bg-gray-900 high-contrast:bg-black">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Data/Hora
|
||||
@ -1950,22 +1950,22 @@ const PainelSecretaria = () => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
<tbody className="bg-white dark:bg-gray-800 high-contrast:bg-black divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
{consultasFiltradas.map((consulta) => (
|
||||
<tr
|
||||
key={consulta.id}
|
||||
className="odd:bg-white even:bg-gray-50 hover:bg-gray-100"
|
||||
className="bg-white dark:bg-gray-800 high-contrast:bg-black hover:bg-gray-100 dark:hover:bg-gray-700 high-contrast:hover:bg-gray-900"
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{formatDateTimeLocal(consulta.dataHora)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{consulta.pacienteNome}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{consulta.medicoNome}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{consulta.tipo}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
@ -1977,7 +1977,7 @@ const PainelSecretaria = () => {
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
className="text-sm border rounded px-2 py-1"
|
||||
className="text-sm border border-gray-300 dark:border-gray-700 high-contrast:border-gray-700 rounded px-2 py-1 bg-white dark:bg-gray-900 high-contrast:bg-black text-gray-900 high-contrast:text-white"
|
||||
>
|
||||
<option value="agendada">Agendada</option>
|
||||
<option value="confirmada">Confirmada</option>
|
||||
@ -2045,7 +2045,7 @@ const PainelSecretaria = () => {
|
||||
)}
|
||||
|
||||
{activeTab === "relatorios" && (
|
||||
<section className="bg-white rounded-lg shadow">
|
||||
<section className="bg-white dark:bg-gray-800 high-contrast:bg-black rounded-lg shadow border border-gray-200 dark:border-gray-700 high-contrast:border-gray-700">
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<h2 className="text-lg font-semibold text-gray-900">
|
||||
@ -2083,8 +2083,8 @@ const PainelSecretaria = () => {
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50">
|
||||
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
<thead className="sticky top-0 z-10 bg-gray-50 dark:bg-gray-900 high-contrast:bg-black">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Número
|
||||
@ -2106,19 +2106,19 @@ const PainelSecretaria = () => {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
<tbody className="bg-white dark:bg-gray-800 high-contrast:bg-black divide-y divide-gray-200 dark:divide-gray-700 high-contrast:divide-gray-700">
|
||||
{relatorios.map((relatorio) => (
|
||||
<tr
|
||||
key={relatorio.id}
|
||||
className="odd:bg-white even:bg-gray-50 hover:bg-gray-100"
|
||||
className="bg-white dark:bg-gray-800 high-contrast:bg-black hover:bg-gray-100 dark:hover:bg-gray-700 high-contrast:hover:bg-gray-900"
|
||||
>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 high-contrast:text-white">
|
||||
{relatorio.order_number}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{relatorio.exam}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900 high-contrast:text-white">
|
||||
{pacientes.find(
|
||||
(p) => p.id === relatorio.patient_id
|
||||
)?.nome || relatorio.patient_id}
|
||||
@ -2127,12 +2127,12 @@ const PainelSecretaria = () => {
|
||||
<span
|
||||
className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${
|
||||
relatorio.status === "draft"
|
||||
? "bg-gray-100 text-gray-800"
|
||||
? "bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-200 high-contrast:bg-gray-900 high-contrast:text-white"
|
||||
: relatorio.status === "completed"
|
||||
? "bg-green-100 text-green-800"
|
||||
? "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200 high-contrast:bg-green-900 high-contrast:text-white"
|
||||
: relatorio.status === "pending"
|
||||
? "bg-yellow-100 text-yellow-800"
|
||||
: "bg-red-100 text-red-800"
|
||||
? "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200 high-contrast:bg-yellow-900 high-contrast:text-white"
|
||||
: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200 high-contrast:bg-red-900 high-contrast:text-white"
|
||||
}`}
|
||||
>
|
||||
{relatorio.status === "draft"
|
||||
|
||||
@ -400,6 +400,65 @@ class AvailabilityService {
|
||||
});
|
||||
return { success: true, data: summary };
|
||||
}
|
||||
|
||||
// Compatibilidade: método utilitário para retornar a disponibilidade semanal
|
||||
// no formato ApiResponse esperado pelos componentes existentes.
|
||||
async getAvailability(
|
||||
doctorId: string
|
||||
): Promise<
|
||||
ApiResponse<any>
|
||||
> {
|
||||
try {
|
||||
// Alguns componentes esperam a disponibilidade semanal (com chaves
|
||||
// domingo/segunda/..). Tentar obter via listAvailability (tabelas
|
||||
// granular por weekday) e transformar se necessário.
|
||||
const res = await this.listAvailability({ doctor_id: doctorId });
|
||||
if (!res.success || !res.data) {
|
||||
return { success: false, error: res.error || "Nenhuma disponibilidade" };
|
||||
}
|
||||
|
||||
// Se o backend já retornar o objeto semanal (com campos domingo..sabado),
|
||||
// apenas repassar. Caso contrário, agrupar os registros por weekday
|
||||
// para manter compatibilidade com componentes que esperam esse formato.
|
||||
const first = res.data[0];
|
||||
const looksLikeWeekly = first && (first as any).domingo !== undefined;
|
||||
if (looksLikeWeekly) {
|
||||
return { success: true, data: res.data };
|
||||
}
|
||||
|
||||
// Agrupar registros por weekday (convertido para PT-BR em listAvailability)
|
||||
const weekly: Record<string, any> = {
|
||||
domingo: { ativo: false, horarios: [] },
|
||||
segunda: { ativo: false, horarios: [] },
|
||||
terca: { ativo: false, horarios: [] },
|
||||
quarta: { ativo: false, horarios: [] },
|
||||
quinta: { ativo: false, horarios: [] },
|
||||
sexta: { ativo: false, horarios: [] },
|
||||
sabado: { ativo: false, horarios: [] },
|
||||
};
|
||||
|
||||
res.data.forEach((item: any) => {
|
||||
const wd = item.weekday as any; // segunda/terca/...
|
||||
if (!wd) return;
|
||||
// Converter cada registro para formato { ativo, horarios: [{inicio,fim,ativo}] }
|
||||
const entry = {
|
||||
ativo: item.active ?? item.ativo ?? true,
|
||||
horarios: [
|
||||
{
|
||||
inicio: item.start_time || item.inicio || "",
|
||||
fim: item.end_time || item.fim || "",
|
||||
ativo: item.active ?? item.ativo ?? true,
|
||||
},
|
||||
],
|
||||
};
|
||||
weekly[wd] = entry;
|
||||
});
|
||||
|
||||
return { success: true, data: [weekly] };
|
||||
} catch (e) {
|
||||
return { success: false, error: e instanceof Error ? e.message : "Erro desconhecido" };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const availabilityService = new AvailabilityService();
|
||||
|
||||
@ -67,6 +67,20 @@ interface RawConsulta {
|
||||
}
|
||||
|
||||
class ConsultasService {
|
||||
async listarTodas(): Promise<ApiResponse<Consulta[]>> {
|
||||
try {
|
||||
const response = await api.get(ENDPOINTS.CONSULTATIONS, {
|
||||
params: { select: "*" },
|
||||
});
|
||||
const data: RawConsulta[] = Array.isArray(response.data)
|
||||
? response.data
|
||||
: response.data?.data || [];
|
||||
return { success: true, data: data.map(this.mapConsulta) };
|
||||
} catch (error) {
|
||||
console.error("Erro ao listar todas as consultas:", error);
|
||||
return { success: false, error: "Erro ao listar todas as consultas" };
|
||||
}
|
||||
}
|
||||
async listarPorPaciente(
|
||||
pacienteId: string,
|
||||
params?: { futureOnly?: boolean; limit?: number; sort?: "asc" | "desc" }
|
||||
|
||||
@ -37,6 +37,7 @@ export interface Paciente {
|
||||
numeroCarteirinha?: string;
|
||||
observacoes?: string | null;
|
||||
vip?: boolean; // derivado de flags/tags legado
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
export interface Pagination {
|
||||
@ -86,6 +87,7 @@ interface PacienteApi extends Partial<PatientSchema> {
|
||||
vip?: boolean | string | number;
|
||||
is_vip?: boolean | string | number;
|
||||
vip_status?: boolean | string | number;
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
const TRUTHY_STRINGS = new Set(["true", "1", "yes", "sim", "vip", "ativo"]);
|
||||
@ -154,6 +156,7 @@ function mapPacienteFromApi(p: PacienteApi): Paciente {
|
||||
numeroCarteirinha: p.numeroCarteirinha || p.numero_carteirinha,
|
||||
observacoes: p.observacoes || null,
|
||||
vip: extractVipFlag(p),
|
||||
avatar_url: p.avatar_url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user