diff --git a/package-lock.json b/package-lock.json index 155b8c3..64f1928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2631,9 +2631,9 @@ } }, "node_modules/@ckeditor/ckeditor5-alignment/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -3144,9 +3144,9 @@ } }, "node_modules/@ckeditor/ckeditor5-autosave/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -4126,9 +4126,9 @@ } }, "node_modules/@ckeditor/ckeditor5-bookmark/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -7241,9 +7241,9 @@ } }, "node_modules/@ckeditor/ckeditor5-clipboard/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -7758,9 +7758,9 @@ } }, "node_modules/@ckeditor/ckeditor5-code-block/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -8284,9 +8284,9 @@ } }, "node_modules/@ckeditor/ckeditor5-editor-balloon/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -8800,9 +8800,9 @@ } }, "node_modules/@ckeditor/ckeditor5-editor-decoupled/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -8903,9 +8903,9 @@ } }, "node_modules/@ckeditor/ckeditor5-editor-inline/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9006,9 +9006,9 @@ } }, "node_modules/@ckeditor/ckeditor5-editor-multi-root/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9125,9 +9125,9 @@ } }, "node_modules/@ckeditor/ckeditor5-emoji/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9235,9 +9235,9 @@ } }, "node_modules/@ckeditor/ckeditor5-enter/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9750,9 +9750,9 @@ } }, "node_modules/@ckeditor/ckeditor5-find-and-replace/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9853,9 +9853,9 @@ } }, "node_modules/@ckeditor/ckeditor5-font/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -9972,9 +9972,9 @@ } }, "node_modules/@ckeditor/ckeditor5-fullscreen/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -10486,9 +10486,9 @@ } }, "node_modules/@ckeditor/ckeditor5-highlight/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -10589,9 +10589,9 @@ } }, "node_modules/@ckeditor/ckeditor5-horizontal-line/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -10692,9 +10692,9 @@ } }, "node_modules/@ckeditor/ckeditor5-html-embed/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -10902,9 +10902,9 @@ } }, "node_modules/@ckeditor/ckeditor5-html-support/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -11848,9 +11848,9 @@ } }, "node_modules/@ckeditor/ckeditor5-language/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -12790,9 +12790,9 @@ } }, "node_modules/@ckeditor/ckeditor5-markdown-gfm/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -13319,9 +13319,9 @@ } }, "node_modules/@ckeditor/ckeditor5-mention/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -13421,9 +13421,9 @@ } }, "node_modules/@ckeditor/ckeditor5-minimap/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -13524,9 +13524,9 @@ } }, "node_modules/@ckeditor/ckeditor5-page-break/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14062,9 +14062,9 @@ } }, "node_modules/@ckeditor/ckeditor5-remove-format/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14165,9 +14165,9 @@ } }, "node_modules/@ckeditor/ckeditor5-restricted-editing/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14267,9 +14267,9 @@ } }, "node_modules/@ckeditor/ckeditor5-select-all/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14369,9 +14369,9 @@ } }, "node_modules/@ckeditor/ckeditor5-show-blocks/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14472,9 +14472,9 @@ } }, "node_modules/@ckeditor/ckeditor5-source-editing/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14588,9 +14588,9 @@ } }, "node_modules/@ckeditor/ckeditor5-special-characters/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -14744,9 +14744,9 @@ } }, "node_modules/@ckeditor/ckeditor5-style/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15256,9 +15256,9 @@ } }, "node_modules/@ckeditor/ckeditor5-theme-lark/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15384,9 +15384,9 @@ } }, "node_modules/@ckeditor/ckeditor5-undo/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15483,9 +15483,9 @@ } }, "node_modules/@ckeditor/ckeditor5-upload/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15594,9 +15594,9 @@ } }, "node_modules/@ckeditor/ckeditor5-watchdog/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15712,9 +15712,9 @@ } }, "node_modules/@ckeditor/ckeditor5-widget/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -15814,9 +15814,9 @@ } }, "node_modules/@ckeditor/ckeditor5-word-count/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -19654,9 +19654,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", - "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "license": "MIT", "peer": true, "peerDependencies": { @@ -22104,9 +22104,9 @@ } }, "node_modules/ckeditor5/node_modules/color-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.0.2.tgz", - "integrity": "sha512-9vEt7gE16EW7Eu7pvZnR0abW9z6ufzhXxGXZEVU9IqPdlsUiMwJeJfRtq0zePUmnbHGT9zajca7mX8zgoayo4A==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz", + "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==", "license": "MIT", "peer": true, "engines": { @@ -29007,9 +29007,9 @@ } }, "node_modules/mdast-util-to-hast": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", - "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "peer": true, "dependencies": { @@ -31977,6 +31977,23 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/src/PagesAdm/gestao.css b/src/PagesAdm/gestao.css index 5991041..9951eae 100644 --- a/src/PagesAdm/gestao.css +++ b/src/PagesAdm/gestao.css @@ -1,4 +1,3 @@ - .dashboard-container { padding: 2rem; font-family: 'Arial', sans-serif; @@ -6,7 +5,6 @@ min-height: 100vh; } - .dashboard-header { display: flex; justify-content: space-between; @@ -34,143 +32,133 @@ border: none; border-radius: 8px; font-size: 1rem; + font-weight: 600; cursor: pointer; - transition: background-color 0.3s, transform 0.25s ease, box-shadow 0.25s ease; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(30, 58, 138, 0.3); } .new-user-btn:hover { background-color: #162d6b; transform: translateY(-2px); - box-shadow: 0px 4px 12px rgba(30, 58, 138, 0.3); + box-shadow: 0 4px 12px rgba(30, 58, 138, 0.4); } - .filters-container { background: #fff; border-radius: 12px; - padding: 1.5rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 1.2rem; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-bottom: 2rem; - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.filters-container:hover { - transform: translateY(-3px); - box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); } .filters-title { - font-size: 18px; + font-size: 16px; font-weight: bold; margin-bottom: 0.3rem; color: #333; } .filters-subtitle { - font-size: 0.9rem; + font-size: 0.85rem; color: #666; margin-bottom: 1rem; } .filters-content { display: flex; - gap: 1rem; + gap: 0.8rem; align-items: center; } .filters-input { flex: 1; - padding: 0.6rem 1rem; + padding: 0.5rem 0.8rem; border: 1px solid #d1d5db; - border-radius: 8px; - font-size: 0.95rem; + border-radius: 6px; + font-size: 0.9rem; color: #333; - transition: border-color 0.2s, box-shadow 0.2s; + min-width: 200px; + transition: all 0.2s ease; } .filters-input:focus { border-color: #1e3a8a; - box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2); + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.1); outline: none; } .filters-select { - padding: 0.6rem 1rem; + padding: 0.5rem 0.8rem; border: 1px solid #d1d5db; - border-radius: 8px; - font-size: 0.95rem; + border-radius: 6px; + font-size: 0.9rem; background: #fff; color: #333; cursor: pointer; - transition: border-color 0.2s, box-shadow 0.2s; + min-width: 140px; + transition: all 0.2s ease; } .filters-select:focus { border-color: #1e3a8a; - box-shadow: 0px 0px 0px 3px rgba(30, 58, 138, 0.2); + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.1); outline: none; } - .cards-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1.5rem; + gap: 1.2rem; margin-bottom: 2rem; } .card { background-color: white; - padding: 1.5rem; - border-radius: 12px; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + padding: 1.2rem; + border-radius: 10px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); border: 1px solid transparent; - transition: transform 0.25s ease, box-shadow 0.25s ease, border 0.25s ease, background 0.25s ease; + transition: all 0.25s ease; cursor: pointer; } .highlight:hover { - transform: translateY(-6px); - box-shadow: 0 8px 20px rgba(30, 58, 138, 0.2); + transform: translateY(-4px); + box-shadow: 0 6px 16px rgba(30, 58, 138, 0.2); background: #f8faff; border: 1px solid #1e3a8a33; } .card-label { - font-size: 0.9rem; + font-size: 0.85rem; color: #999; margin-bottom: 0.5rem; } .card-value { - font-size: 1.8rem; + font-size: 1.6rem; font-weight: bold; margin: 0; color: #333; } .card-extra { - font-size: 0.85rem; + font-size: 0.8rem; color: #666; } .card-extra.positive { color: #1e3a8a; + font-weight: 600; } - .user-table-container { background: #fff; border-radius: 12px; - padding: 1.5rem; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); + padding: 1.2rem; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-top: 2rem; - transition: transform 0.25s ease, box-shadow 0.25s ease; -} - -.user-table-container:hover { - transform: translateY(-4px); - box-shadow: 0 6px 14px rgba(0,0,0,0.15); } .user-table-container h2 { @@ -193,7 +181,7 @@ .user-table th, .user-table td { - padding: 12px 15px; + padding: 10px 12px; text-align: left; border-bottom: 1px solid #e0e0e0; } @@ -202,10 +190,11 @@ background-color: #f3f4f6; color: #333; font-weight: 600; + font-size: 0.9rem; } .user-table tr { - transition: background-color 0.25s ease; + transition: background-color 0.2s ease; } .user-table tr:hover { @@ -214,44 +203,115 @@ .profile-badge { background-color: #1e3a8a; - color: #f7f7f7; - padding: 3px 8px; - border-radius: 8px; - font-size: 0.85rem; + color: white; + padding: 4px 10px; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; display: inline-block; } .status-badge { - padding: 3px 8px; - border-radius: 8px; - font-size: 0.85rem; + padding: 4px 10px; + border-radius: 6px; + font-size: 0.8rem; color: #fff; + font-weight: 500; display: inline-block; text-transform: capitalize; } .status-badge.ativo { - background-color: #28a745; + background-color: #1e3a8a; } .status-badge.inativo { - background-color: #dc3545; + background-color: #6c757d; } .actions { display: flex; - gap: 10px; + gap: 8px; + flex-wrap: wrap; } -.action-icon { +.action-btn { + border: none; + padding: 6px 12px; + font-size: 0.8rem; + font-weight: 500; cursor: pointer; - color: #555; - transition: color 0.2s, transform 0.2s; + transition: all 0.2s ease; + border-radius: 4px; + display: inline-flex; + align-items: center; + gap: 4px; } -.action-icon:hover { - color: #1e3a8a; - transform: scale(1.2); +.action-btn.detalhes { + background-color: #e6f2ff; + color: #004085; + border: 1px solid #b8d4ff; +} + +.action-btn.detalhes:hover { + background-color: #cce4ff; + transform: translateY(-1px); +} + +.action-btn.editar { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; +} + +.action-btn.editar:hover { + background-color: #ffeaa7; + transform: translateY(-1px); +} + +.action-btn.excluir { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f1b0b7; +} + +.action-btn.excluir:hover { + background-color: #f1b0b7; + transform: translateY(-1px); +} + +.save-btn { + background-color: #1e3a8a; + color: white; + border: none; + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.save-btn:hover { + background-color: #162d6b; + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(30, 58, 138, 0.3); +} + +.edit-btn { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; + padding: 8px 16px; + border-radius: 6px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.edit-btn:hover { + background-color: #ffeaa7; + transform: translateY(-1px); } html[data-bs-theme="dark"] .dashboard-container { @@ -266,18 +326,17 @@ html[data-bs-theme="dark"] .dashboard-subtitle { } html[data-bs-theme="dark"] .new-user-btn { - background-color: #2563eb; - color: #fff; + background-color: #1e3a8a; } html[data-bs-theme="dark"] .new-user-btn:hover { - background-color: #1e40af; + background-color: #162d6b; } html[data-bs-theme="dark"] .filters-container, html[data-bs-theme="dark"] .user-table-container { background: #1a1a1a; - box-shadow: 0 4px 6px rgba(0,0,0,0.4); + box-shadow: 0 2px 8px rgba(0,0,0,0.4); } html[data-bs-theme="dark"] .filters-title, @@ -299,19 +358,19 @@ html[data-bs-theme="dark"] .filters-select { html[data-bs-theme="dark"] .filters-input:focus, html[data-bs-theme="dark"] .filters-select:focus { - border-color: #2563eb; - box-shadow: 0px 0px 0px 3px rgba(37, 99, 235, 0.2); + border-color: #1e3a8a; + box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.2); } html[data-bs-theme="dark"] .cards-container .card { background-color: #181818; color: #e0e0e0; - box-shadow: 0 4px 6px rgba(0,0,0,0.4); + box-shadow: 0 2px 6px rgba(0,0,0,0.4); } html[data-bs-theme="dark"] .highlight:hover { - background: #232a3a; - border: 1px solid #2563eb33; + background: #1a1f2e; + border: 1px solid #1e3a8a33; } html[data-bs-theme="dark"] .card-label { @@ -327,7 +386,7 @@ html[data-bs-theme="dark"] .card-extra { } html[data-bs-theme="dark"] .card-extra.positive { - color: #2563eb; + color: #1e3a8a; } html[data-bs-theme="dark"] .user-table th { @@ -341,26 +400,39 @@ html[data-bs-theme="dark"] .user-table td { } html[data-bs-theme="dark"] .user-table tr:hover { - background-color: #232a3a; + background-color: #1a1f2e; } html[data-bs-theme="dark"] .profile-badge { - background-color: #2563eb; - color: #fff; + background-color: #1e3a8a; } -html[data-bs-theme="dark"] .status-badge.ativo { - background-color: #28a745; +html[data-bs-theme="dark"] .action-btn.detalhes { + background-color: #e6f2ff; + color: #004085; + border: 1px solid #b8d4ff; } -html[data-bs-theme="dark"] .status-badge.inativo { - background-color: #dc3545; +html[data-bs-theme="dark"] .action-btn.detalhes:hover { + background-color: #cce4ff; } -html[data-bs-theme="dark"] .action-icon { - color: #bdbdbd; +html[data-bs-theme="dark"] .action-btn.editar { + background-color: #fff3cd; + color: #856405; + border: 1px solid #ffeaa7; } -html[data-bs-theme="dark"] .action-icon:hover { - color: #2563eb; +html[data-bs-theme="dark"] .action-btn.editar:hover { + background-color: #ffeaa7; +} + +html[data-bs-theme="dark"] .action-btn.excluir { + background-color: #f8d7da; + color: #721c24; + border: 1px solid #f1b0b7; +} + +html[data-bs-theme="dark"] .action-btn.excluir:hover { + background-color: #f1b0b7; } \ No newline at end of file diff --git a/src/PagesAdm/gestao.jsx b/src/PagesAdm/gestao.jsx index 7d982b9..a5ecd88 100644 --- a/src/PagesAdm/gestao.jsx +++ b/src/PagesAdm/gestao.jsx @@ -1,14 +1,9 @@ - import React from "react"; import "./gestao.css"; -import { FaEdit, FaTrash } from "react-icons/fa"; - function UserDashboard() { return ( - -
- +

Gestão de Usuários

@@ -91,8 +86,9 @@ function UserDashboard() { Ativo 20/12/2024, 08:30 - - + + + @@ -103,8 +99,9 @@ function UserDashboard() { Ativo 19/12/2024, 14:20 - - + + + @@ -115,8 +112,9 @@ function UserDashboard() { Ativo 20/12/2024, 07:45 - - + + + @@ -127,8 +125,9 @@ function UserDashboard() { Inativo 15/12/2024, 16:30 - - + + + @@ -138,5 +137,4 @@ function UserDashboard() { ); } - export default UserDashboard; \ No newline at end of file diff --git a/src/PagesMedico/DoctorAgendamentoManager.jsx b/src/PagesMedico/DoctorAgendamentoManager.jsx index acee929..567646c 100644 --- a/src/PagesMedico/DoctorAgendamentoManager.jsx +++ b/src/PagesMedico/DoctorAgendamentoManager.jsx @@ -1,785 +1,1034 @@ -import React, { useState, useMemo, useEffect, useCallback } from 'react'; -import { useNavigate } from 'react-router-dom'; -import API_KEY from '../components/utils/apiKeys.js'; -import AgendamentoCadastroManager from '../pages/AgendamentoCadastroManager.jsx'; -import { GetAllDoctors } from '../components/utils/Functions-Endpoints/Doctor.js'; -import { useAuth } from '../components/utils/AuthProvider.js'; -import dayjs from 'dayjs'; -import 'dayjs/locale/pt-br'; -import isBetween from 'dayjs/plugin/isBetween'; -import localeData from 'dayjs/plugin/localeData'; -import { Search, ChevronLeft, ChevronRight, Edit, Trash2, CheckCircle } from 'lucide-react'; +import React, { useState, useMemo, useEffect, useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import API_KEY from "../components/utils/apiKeys.js"; +import AgendamentoCadastroManager from "../pages/AgendamentoCadastroManager.jsx"; +import { GetAllDoctors } from "../components/utils/Functions-Endpoints/Doctor.js"; +import { useAuth } from "../components/utils/AuthProvider.js"; +import dayjs from "dayjs"; +import "dayjs/locale/pt-br"; +import isBetween from "dayjs/plugin/isBetween"; +import localeData from "dayjs/plugin/localeData"; +import { + Search, + ChevronLeft, + ChevronRight, + Edit, + Trash2, + CheckCircle, +} from "lucide-react"; import "../pages/style/Agendamento.css"; -import '../pages/style/FilaEspera.css'; -import Spinner from '../components/Spinner.jsx'; +import "../pages/style/FilaEspera.css"; +import Spinner from "../components/Spinner.jsx"; - -dayjs.locale('pt-br'); +dayjs.locale("pt-br"); dayjs.extend(isBetween); dayjs.extend(localeData); - const Agendamento = () => { - const [searchTerm, setSearchTerm] = useState(''); - const navigate = useNavigate(); - const { getAuthorizationHeader, user } = useAuth(); - const authHeader = getAuthorizationHeader(); + const navigate = useNavigate(); + const { getAuthorizationHeader, user } = useAuth(); + const authHeader = getAuthorizationHeader(); - const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; + const ID_MEDICO_ESPECIFICO = "078d2a67-b4c1-43c8-ae32-c1e75bb5b3df"; - const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); - const [selectedID, setSelectedId] = useState('0'); - const [filaEsperaData, setFilaEsperaData] = useState([]); - const [FiladeEspera, setFiladeEspera] = useState(false); - const [PageNovaConsulta, setPageConsulta] = useState(false); - const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState({}); - const [showDeleteModal, setShowDeleteModal] = useState(false); - const [ListaDeMedicos, setListaDeMedicos] = useState([]); - const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); - const [searchTermDoctor, setSearchTermDoctor] = useState(''); - const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); - const [motivoCancelamento, setMotivoCancelamento] = useState(""); - const [showSpinner, setShowSpinner] = useState(true); - const [waitlistSearch, setWaitlistSearch] = useState(''); - const [waitSortKey, setWaitSortKey] = useState(null); - const [waitSortDir, setWaitSortDir] = useState('asc'); - const [waitPage, setWaitPage] = useState(1); - const [waitPerPage, setWaitPerPage] = useState(10); - const [cacheMedicos, setCacheMedicos] = useState({}); - const [cachePacientes, setCachePacientes] = useState({}); - const [currentDate, setCurrentDate] = useState(dayjs()); - const [selectedDay, setSelectedDay] = useState(dayjs()); - const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null); - const [quickJump, setQuickJump] = useState({ - - month: currentDate.month(), - year: currentDate.year() - }); + const [listaTodosAgendamentos, setListaTodosAgendamentos] = useState([]); + const [selectedID, setSelectedId] = useState("0"); + const [filaEsperaData, setFilaEsperaData] = useState([]); + const [FiladeEspera, setFiladeEspera] = useState(false); + const [PageNovaConsulta, setPageConsulta] = useState(false); + const [DictAgendamentosOrganizados, setAgendamentosOrganizados] = useState( + {} + ); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [ListaDeMedicos, setListaDeMedicos] = useState([]); + const [FiltredTodosMedicos, setFiltredTodosMedicos] = useState([]); + const [searchTermDoctor, setSearchTermDoctor] = useState(""); + const [MedicoFiltrado, setMedicoFiltrado] = useState({ id: "vazio" }); + const [motivoCancelamento, setMotivoCancelamento] = useState(""); + const [showSpinner, setShowSpinner] = useState(true); + const [waitlistSearch, setWaitlistSearch] = useState(""); + const [waitSortKey, setWaitSortKey] = useState(null); + const [waitSortDir, setWaitSortDir] = useState("asc"); + const [waitPage, setWaitPage] = useState(1); + const [waitPerPage, setWaitPerPage] = useState(10); + const [cacheMedicos, setCacheMedicos] = useState({}); + const [cachePacientes, setCachePacientes] = useState({}); + const [currentDate, setCurrentDate] = useState(dayjs()); + const [selectedDay, setSelectedDay] = useState(dayjs()); + const [agendamentoParaEdicao, setAgendamentoParaEdicao] = useState(null); + const [quickJump, setQuickJump] = useState({ + month: currentDate.month(), + year: currentDate.year(), + }); - const fetchAppointments = useCallback(async () => { - if (!authHeader) return; - setShowSpinner(true); - const myHeaders = new Headers(); - myHeaders.append("Authorization", authHeader); - myHeaders.append("apikey", API_KEY); - const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' }; + const fetchAppointments = useCallback(async () => { + if (!authHeader) return; + setShowSpinner(true); + const myHeaders = new Headers(); + myHeaders.append("Authorization", authHeader); + myHeaders.append("apikey", API_KEY); + const requestOptions = { + method: "GET", + headers: myHeaders, + redirect: "follow", + }; - const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`; + const apiUrl = `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?doctor_id=eq.${ID_MEDICO_ESPECIFICO}&select=*`; - try { - const res = await fetch(apiUrl, requestOptions); - const data = await res.json(); - setListaTodosAgendamentos(data || []); - } catch (err) { - console.error('Erro ao buscar agendamentos', err); - setListaTodosAgendamentos([]); - } finally { - setShowSpinner(false); - } - }, [authHeader, ID_MEDICO_ESPECIFICO]); + try { + const res = await fetch(apiUrl, requestOptions); + const data = await res.json(); + setListaTodosAgendamentos(data || []); + } catch (err) { + console.error("Erro ao buscar agendamentos", err); + setListaTodosAgendamentos([]); + } finally { + setShowSpinner(false); + } + }, [authHeader, ID_MEDICO_ESPECIFICO]); - - const updateAppointmentStatus = useCallback(async (id, updates) => { - setShowSpinner(true); - const myHeaders = new Headers(); - myHeaders.append("Authorization", authHeader); - myHeaders.append("apikey", API_KEY); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("Prefer", "return=representation"); - const requestOptions = { method: 'PATCH', headers: myHeaders, body: JSON.stringify(updates) }; - try { - const response = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, requestOptions); - if (response.ok) { - await fetchAppointments(); - return true; - } else { - console.error('Erro ao atualizar agendamento:', await response.text()); - return false; - } - } catch (error) { - console.error('Erro de rede/servidor:', error); - return false; - } finally { - setShowSpinner(false); - } - }, [authHeader, fetchAppointments]); - - - const deleteConsulta = useCallback(async (id) => { - const success = await updateAppointmentStatus(id, { status: "cancelled", cancellation_reason: motivoCancelamento, updated_at: new Date().toISOString() }); - if (success) { - setShowDeleteModal(false); - setMotivoCancelamento(""); - setSelectedId('0'); + const updateAppointmentStatus = useCallback( + async (id, updates) => { + setShowSpinner(true); + const myHeaders = new Headers(); + myHeaders.append("Authorization", authHeader); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("Prefer", "return=representation"); + const requestOptions = { + method: "PATCH", + headers: myHeaders, + body: JSON.stringify(updates), + }; + try { + const response = await fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/appointments?id=eq.${id}`, + requestOptions + ); + if (response.ok) { + await fetchAppointments(); + return true; } else { - alert("Falha ao cancelar a consulta."); + console.error( + "Erro ao atualizar agendamento:", + await response.text() + ); + return false; } - }, [motivoCancelamento, updateAppointmentStatus]); + } catch (error) { + console.error("Erro de rede/servidor:", error); + return false; + } finally { + setShowSpinner(false); + } + }, + [authHeader, fetchAppointments] + ); - const confirmConsulta = useCallback(async (id) => { - const success = await updateAppointmentStatus(id, { status: "agendado", cancellation_reason: null, updated_at: new Date().toISOString() }); - if (success) { - setSelectedId('0'); - } else { - alert("Falha ao reverter o cancelamento."); + const deleteConsulta = useCallback( + async (id) => { + const success = await updateAppointmentStatus(id, { + status: "cancelled", + cancellation_reason: motivoCancelamento, + updated_at: new Date().toISOString(), + }); + if (success) { + setShowDeleteModal(false); + setMotivoCancelamento(""); + setSelectedId("0"); + } else { + alert("Falha ao cancelar a consulta."); + } + }, + [motivoCancelamento, updateAppointmentStatus] + ); + + const confirmConsulta = useCallback( + async (id) => { + const success = await updateAppointmentStatus(id, { + status: "agendado", + cancellation_reason: null, + updated_at: new Date().toISOString(), + }); + if (success) { + setSelectedId("0"); + } else { + alert("Falha ao reverter o cancelamento."); + } + }, + [updateAppointmentStatus] + ); + + useEffect(() => { + if (authHeader) { + fetchAppointments(); + + if (user?.role !== "doctor") { + GetAllDoctors(authHeader).then((docs) => { + if (docs) { + setListaDeMedicos( + docs.map((d) => ({ nomeMedico: d.full_name, idMedico: d.id })) + ); + } + }); + } + } + }, [authHeader, fetchAppointments, user?.role]); + + useEffect(() => { + const processData = async () => { + if (!listaTodosAgendamentos.length) { + setAgendamentosOrganizados({}); + setFilaEsperaData([]); + return; + } + + setShowSpinner(true); + + const appointmentsToShow = listaTodosAgendamentos; + + const patientIdsToFetch = new Set(); + const doctorIdsToFetch = new Set(); + + appointmentsToShow.forEach((ag) => { + if (ag.patient_id && !cachePacientes[ag.patient_id]) { + patientIdsToFetch.add(ag.patient_id); } - }, [updateAppointmentStatus]); - - useEffect(() => { - if(authHeader) { - fetchAppointments(); - if (user?.role !== 'doctor') { - GetAllDoctors(authHeader).then(docs => { - if (docs) { - setListaDeMedicos(docs.map(d => ({ nomeMedico: d.full_name, idMedico: d.id }))); - } - }); - } + if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) { + doctorIdsToFetch.add(ag.doctor_id); } - }, [authHeader, fetchAppointments, user?.role]); + }); - useEffect(() => { - const processData = async () => { - if (!listaTodosAgendamentos.length) { - setAgendamentosOrganizados({}); - setFilaEsperaData([]); - return; + const fetchPromises = []; + + if (patientIdsToFetch.size > 0) { + const query = `id=in.(${Array.from(patientIdsToFetch).join(",")})`; + fetchPromises.push( + fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?${query}&select=*`, + { + headers: { apikey: API_KEY, Authorization: authHeader }, } + ).then((res) => res.json()) + ); + } else { + fetchPromises.push(Promise.resolve(null)); + } - setShowSpinner(true); - - const appointmentsToShow = listaTodosAgendamentos; - - const patientIdsToFetch = new Set(); - const doctorIdsToFetch = new Set(); - - appointmentsToShow.forEach(ag => { - if (ag.patient_id && !cachePacientes[ag.patient_id]) { - patientIdsToFetch.add(ag.patient_id); - } - if (ag.doctor_id && !cacheMedicos[ag.doctor_id]) { - doctorIdsToFetch.add(ag.doctor_id); - } - }); - - const fetchPromises = []; - - if (patientIdsToFetch.size > 0) { - const query = `id=in.(${Array.from(patientIdsToFetch).join(',')})`; - fetchPromises.push( - fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?${query}&select=*`, { - headers: { apikey: API_KEY, Authorization: authHeader } - }).then(res => res.json()) - ); - } else { - fetchPromises.push(Promise.resolve(null)); + if (doctorIdsToFetch.size > 0) { + const query = `id=in.(${Array.from(doctorIdsToFetch).join(",")})`; + fetchPromises.push( + fetch( + `https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`, + { + headers: { apikey: API_KEY, Authorization: authHeader }, } + ).then((res) => res.json()) + ); + } else { + fetchPromises.push(Promise.resolve(null)); + } - if (doctorIdsToFetch.size > 0) { - const query = `id=in.(${Array.from(doctorIdsToFetch).join(',')})`; - fetchPromises.push( - fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?${query}&select=id,full_name`, { - headers: { apikey: API_KEY, Authorization: authHeader } - }).then(res => res.json()) - ); - } else { - fetchPromises.push(Promise.resolve(null)); - } - - const [newPatients, newDoctors] = await Promise.all(fetchPromises); + const [newPatients, newDoctors] = await Promise.all(fetchPromises); - const updatedPatientCache = { ...cachePacientes }; - if (newPatients) newPatients.forEach(p => updatedPatientCache[p.id] = p); + const updatedPatientCache = { ...cachePacientes }; + if (newPatients) + newPatients.forEach((p) => (updatedPatientCache[p.id] = p)); - const updatedDoctorCache = { ...cacheMedicos }; - if (newDoctors) newDoctors.forEach(d => updatedDoctorCache[d.id] = d); - - setCachePacientes(updatedPatientCache); - setCacheMedicos(updatedDoctorCache); + const updatedDoctorCache = { ...cacheMedicos }; + if (newDoctors) newDoctors.forEach((d) => (updatedDoctorCache[d.id] = d)); - const newDict = {}; - const newFila = []; + setCachePacientes(updatedPatientCache); + setCacheMedicos(updatedDoctorCache); - for (const agendamento of appointmentsToShow) { - const medico = updatedDoctorCache[agendamento.doctor_id]; - const paciente = updatedPatientCache[agendamento.patient_id]; + const newDict = {}; + const newFila = []; - if (!medico || !paciente) continue; + for (const agendamento of appointmentsToShow) { + const medico = updatedDoctorCache[agendamento.doctor_id]; + const paciente = updatedPatientCache[agendamento.patient_id]; - const agendamentoMelhorado = { - ...agendamento, - medico_nome: medico.full_name || 'N/A', - paciente_nome: paciente.full_name || 'N/A', - paciente_cpf: paciente.cpf || 'N/A' - }; + if (!medico || !paciente) continue; - if (agendamento.status === "requested") { - newFila.push({ agendamento: agendamentoMelhorado, Infos: agendamentoMelhorado }); - } else { - const DiaAgendamento = dayjs(agendamento.scheduled_at).format("YYYY-MM-DD"); - if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = []; - newDict[DiaAgendamento].push(agendamentoMelhorado); - } - } - - for (const key in newDict) { - newDict[key].sort((a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at)); - } - - setAgendamentosOrganizados(newDict); - setFilaEsperaData(newFila); - setShowSpinner(false); + const agendamentoMelhorado = { + ...agendamento, + medico_nome: medico.full_name || "N/A", + paciente_nome: paciente.full_name || "N/A", + paciente_cpf: paciente.cpf || "N/A", }; - processData(); - }, [listaTodosAgendamentos, authHeader]); - - const handleEditConsulta = (agendamento) => { - setAgendamentoParaEdicao(agendamento); - setPageConsulta(true); - }; - - const handleSearchMedicos = (term) => { - setSearchTermDoctor(term); - if (term.trim()) { - const filtered = ListaDeMedicos.filter(medico => - medico.nomeMedico.toLowerCase().includes(term.toLowerCase()) - ); - setFiltredTodosMedicos(filtered); + if (agendamento.status === "requested") { + newFila.push({ + agendamento: agendamentoMelhorado, + Infos: agendamentoMelhorado, + }); } else { - setFiltredTodosMedicos([]); - setMedicoFiltrado({ id: "vazio" }); + const DiaAgendamento = dayjs(agendamento.scheduled_at).format( + "YYYY-MM-DD" + ); + if (!newDict[DiaAgendamento]) newDict[DiaAgendamento] = []; + newDict[DiaAgendamento].push(agendamentoMelhorado); } - }; - - const generateDateGrid = () => { - const grid = []; - const startOfMonth = currentDate.startOf('month'); - let currentDay = startOfMonth.subtract(startOfMonth.day(), 'day'); - for (let i = 0; i < 42; i++) { - grid.push(currentDay); - currentDay = currentDay.add(1, 'day'); - } - return grid; - }; + } - const dateGrid = useMemo(() => generateDateGrid(), [currentDate]); - const weekDays = ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb']; - const handleDateClick = (day) => setSelectedDay(day); - - const DeleteModal = () => ( -
-
-
-
-
Confirmação de Cancelamento
- -
-
-

Qual o motivo do cancelamento? (Opcional)

- -
-
- - -
-
-
-
- ); - - useEffect(() => { - setQuickJump({ - month: currentDate.month(), - year: currentDate.year() - }); - }, [currentDate]); - - const filaEsperaFiltrada = useMemo(() => { - if (!waitlistSearch.trim()) return filaEsperaData; - const term = waitlistSearch.toLowerCase(); - return filaEsperaData.filter(item => - (item?.Infos?.paciente_nome?.toLowerCase() || '').includes(term) || - (item?.Infos?.paciente_cpf?.toLowerCase() || '').includes(term) || - (item?.Infos?.medico_nome?.toLowerCase() || '').includes(term) + for (const key in newDict) { + newDict[key].sort( + (a, b) => new Date(a.scheduled_at) - new Date(b.scheduled_at) ); - }, [waitlistSearch, filaEsperaData]); + } - const applySortingWaitlist = useCallback((arr) => { - if (!Array.isArray(arr) || !waitSortKey) return arr; - const copy = [...arr]; - const key = waitSortKey; - const dir = waitSortDir === 'asc' ? 1 : -1; - copy.sort((a, b) => { - const valA = key === 'data' ? new Date(a.agendamento.scheduled_at) : (a.Infos?.[`${key}_nome`] || ''); - const valB = key === 'data' ? new Date(b.agendamento.scheduled_at) : (b.Infos?.[`${key}_nome`] || ''); - if (valA < valB) return -1 * dir; - if (valA > valB) return 1 * dir; - return 0; - }); - return copy; - }, [waitSortKey, waitSortDir]); - - const filaEsperaOrdenada = useMemo(() => applySortingWaitlist(filaEsperaFiltrada), [filaEsperaFiltrada, applySortingWaitlist]); - const waitTotalPages = Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1; - const waitIndiceInicial = (waitPage - 1) * waitPerPage; - const waitIndiceFinal = waitIndiceInicial + waitPerPage; - const filaEsperaPaginada = filaEsperaOrdenada.slice(waitIndiceInicial, waitIndiceFinal); - - const gerarNumerosWaitPages = () => { - const paginas = []; - const paginasParaMostrar = 5; - let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2)); - let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1); - inicio = Math.max(1, fim - paginasParaMostrar + 1); - for (let i = inicio; i <= fim; i++) paginas.push(i); - return paginas; + setAgendamentosOrganizados(newDict); + setFilaEsperaData(newFila); + setShowSpinner(false); }; - useEffect(() => { setWaitPage(1); }, [waitlistSearch, waitSortKey, waitSortDir]); + processData(); + }, [listaTodosAgendamentos, authHeader]); - const handleQuickJumpChange = (type, value) => { - setQuickJump(prev => ({ ...prev, [type]: Number(value) })); - }; + const handleEditConsulta = (agendamento) => { + setAgendamentoParaEdicao(agendamento); + setPageConsulta(true); + }; - const applyQuickJump = () => { - let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); - setCurrentDate(newDate); - setSelectedDay(newDate); - }; + const handleSearchMedicos = (term) => { + setSearchTermDoctor(term); + if (term.trim()) { + const filtered = ListaDeMedicos.filter((medico) => + medico.nomeMedico.toLowerCase().includes(term.toLowerCase()) + ); + setFiltredTodosMedicos(filtered); + } else { + setFiltredTodosMedicos([]); + setMedicoFiltrado({ id: "vazio" }); + } + }; - return ( -
-

Agendar nova consulta

- {!PageNovaConsulta ? ( -
- {/* ABA + BOTÕES NA MESMA BARRA */} -
-
- - -
+ const generateDateGrid = () => { + const grid = []; + const startOfMonth = currentDate.startOf("month"); + let currentDay = startOfMonth.subtract(startOfMonth.day(), "day"); + for (let i = 0; i < 42; i++) { + grid.push(currentDay); + currentDay = currentDay.add(1, "day"); + } + return grid; + }; -
- - - -
-
- - {/* RESTO DO CONTEÚDO */} -
- {!FiladeEspera ? ( -
-
-
- {selectedDay.format('MMM')} - {selectedDay.format('DD')} -
-
-

{selectedDay.format('dddd')}

-

{selectedDay.format('D [de] MMMM [de] YYYY')}

-
-
-
- Realizado -
-
- Confirmado -
-
- Agendado -
-
- Cancelado -
-
- -
-

Consultas para {selectedDay.format('DD/MM')}

- {showSpinner ? ( - - ) : ( - DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] && - DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] - .filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id) - .length > 0 ? ( - DictAgendamentosOrganizados[selectedDay.format('YYYY-MM-DD')] - .filter(app => MedicoFiltrado.id === "vazio" || app.doctor_id === MedicoFiltrado.id) - .map(app => ( -
-
- {dayjs(app.scheduled_at).format('HH:mm')} -
-
- {app.paciente_nome} - Dr(a). {app.medico_nome} -
-
- {app.status === 'cancelled' ? ( - - ) : ( - - )} - {app.status !== 'cancelled' && ( - - )} -
-
- )) - ) : ( -
-

Nenhuma consulta agendada.

-
- ) - )} -
-
- -
-{/* FILTRO POR PACIENTE DENTRO DO CALENDÁRIO */} -
-
- Filtrar por Paciente -
- setSearchTerm(e.target.value)} - /> - - Buscar paciente para filtrar consultas - -
-
-
-

{currentDate.format('MMMM [de] YYYY')}

-
- - - -
-
- -
- - - -
-
- -
- {weekDays.map(day => ( -
{day}
- ))} - {dateGrid.map((day, index) => { - const dayString = day.format('YYYY-MM-DD'); - const appointmentsOnDay = DictAgendamentosOrganizados[dayString] || []; -const filteredAppointments = appointmentsOnDay.filter(app => - !searchTerm - ? true - : (app.paciente_nome || '').toLowerCase().includes(searchTerm.toLowerCase()) -); - const cellClasses = `day-cell ${ - day.isSame(currentDate, 'month') ? 'current-month' : 'other-month' - } ${day.isSame(dayjs(), 'day') ? 'today' : ''} ${ - day.isSame(selectedDay, 'day') ? 'selected' : '' - }`; - return ( -
handleDateClick(day)} - > - {day.format('D')} - {filteredAppointments.length > 0 && ( -
- {filteredAppointments.length} -
-)} -
- ); - })} -
-
-
- ) : ( -
-
-
-
-
-

Fila de Espera

-
-
-
-
- Filtros -
-
- setWaitlistSearch(e.target.value)} - /> - - Digite o nome do paciente, CPF ou nome do médico - -
-
-
- Ordenar por: - -
-
-
-
- {filaEsperaFiltrada.length} DE {filaEsperaData.length} SOLICITAÇÕES ENCONTRADAS -
-
-
- -
- - - - - - - - - - - - {filaEsperaPaginada.length > 0 ? ( - filaEsperaPaginada.map((item, index) => ( - - - - - - - - )) - ) : ( - - - - )} - -
Nome do PacienteCPFMédico SolicitadoData da SolicitaçãoAções
{item?.Infos?.paciente_nome}{item?.Infos?.paciente_cpf}{item?.Infos?.medico_nome}{dayjs(item.agendamento.scheduled_at).format('DD/MM/YYYY')} - -
-
- {showSpinner ? ( - - ) : ( - <> - -

Nenhuma solicitação encontrada.

- - )} -
-
- - {filaEsperaFiltrada.length > 0 && ( -
-
- Itens por página: - -
-
- - Página {waitPage} de {waitTotalPages} • Mostrando {waitIndiceInicial + 1}-{Math.min(waitIndiceFinal, filaEsperaFiltrada.length)} de {filaEsperaFiltrada.length} - - -
-
- )} -
-
-
-
-
-
- )} -
-
- ) : ( - { - setPageConsulta(false); - fetchAppointments(); - }} - /> - )} - - {showDeleteModal && } + const dateGrid = useMemo(() => generateDateGrid(), [currentDate]); + const weekDays = ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"]; + const handleDateClick = (day) => setSelectedDay(day); + const DeleteModal = () => ( +
+
+
+
+
Confirmação de Cancelamento
+
+
+

Qual o motivo do cancelamento?

+ +
+
+ + +
+
+
+ ); + + useEffect(() => { + setQuickJump({ + month: currentDate.month(), + year: currentDate.year(), + }); + }, [currentDate]); + + const filaEsperaFiltrada = useMemo(() => { + if (!waitlistSearch.trim()) return filaEsperaData; + const term = waitlistSearch.toLowerCase(); + return filaEsperaData.filter( + (item) => + (item?.Infos?.paciente_nome?.toLowerCase() || "").includes(term) || + (item?.Infos?.paciente_cpf?.toLowerCase() || "").includes(term) || + (item?.Infos?.medico_nome?.toLowerCase() || "").includes(term) ); + }, [waitlistSearch, filaEsperaData]); + + const applySortingWaitlist = useCallback( + (arr) => { + if (!Array.isArray(arr) || !waitSortKey) return arr; + const copy = [...arr]; + const key = waitSortKey; + const dir = waitSortDir === "asc" ? 1 : -1; + copy.sort((a, b) => { + const valA = + key === "data" + ? new Date(a.agendamento.scheduled_at) + : a.Infos?.[`${key}_nome`] || ""; + const valB = + key === "data" + ? new Date(b.agendamento.scheduled_at) + : b.Infos?.[`${key}_nome`] || ""; + if (valA < valB) return -1 * dir; + if (valA > valB) return 1 * dir; + return 0; + }); + return copy; + }, + [waitSortKey, waitSortDir] + ); + + const filaEsperaOrdenada = useMemo( + () => applySortingWaitlist(filaEsperaFiltrada), + [filaEsperaFiltrada, applySortingWaitlist] + ); + const waitTotalPages = + Math.ceil(filaEsperaOrdenada.length / waitPerPage) || 1; + const waitIndiceInicial = (waitPage - 1) * waitPerPage; + const waitIndiceFinal = waitIndiceInicial + waitPerPage; + const filaEsperaPaginada = filaEsperaOrdenada.slice( + waitIndiceInicial, + waitIndiceFinal + ); + + const gerarNumerosWaitPages = () => { + const paginas = []; + const paginasParaMostrar = 5; + let inicio = Math.max(1, waitPage - Math.floor(paginasParaMostrar / 2)); + let fim = Math.min(waitTotalPages, inicio + paginasParaMostrar - 1); + inicio = Math.max(1, fim - paginasParaMostrar + 1); + for (let i = inicio; i <= fim; i++) paginas.push(i); + return paginas; + }; + + useEffect(() => { + setWaitPage(1); + }, [waitlistSearch, waitSortKey, waitSortDir]); + + const handleQuickJumpChange = (type, value) => { + setQuickJump((prev) => ({ ...prev, [type]: Number(value) })); + }; + + const applyQuickJump = () => { + let newDate = dayjs().year(quickJump.year).month(quickJump.month).date(1); + setCurrentDate(newDate); + setSelectedDay(newDate); + }; + + return ( +
+

Agendar nova consulta

+ {!PageNovaConsulta ? ( +
+ {user?.role !== "doctor" && ( +
+
+ + Filtrar por Médico +
+
+ handleSearchMedicos(e.target.value)} + /> + + Buscar médico para filtrar consultas + + + {searchTermDoctor && FiltredTodosMedicos.length > 0 && ( +
+ {FiltredTodosMedicos.map((medico) => ( + + ))} +
+ )} +
+ + {MedicoFiltrado.id !== "vazio" && ( +
+ + + {searchTermDoctor} + + +
+ )} +
+ )} +
+
+ + +
+
+ + + +
+
+ +
+ {!FiladeEspera ? ( +
+
+
+ {selectedDay.format("MMM")} + {selectedDay.format("DD")} +
+
+

{selectedDay.format("dddd")}

+

{selectedDay.format("D [de] MMMM [de] YYYY")}

+
+
+

Consultas para {selectedDay.format("DD/MM")}

+ {showSpinner ? ( + + ) : DictAgendamentosOrganizados[ + selectedDay.format("YYYY-MM-DD") + ]?.filter( + (app) => + MedicoFiltrado.id === "vazio" || + app.doctor_id === MedicoFiltrado.id + ).length > 0 ? ( + DictAgendamentosOrganizados[ + selectedDay.format("YYYY-MM-DD") + ] + .filter( + (app) => + MedicoFiltrado.id === "vazio" || + app.doctor_id === MedicoFiltrado.id + ) + .map((app) => ( +
+
+ {dayjs(app.scheduled_at).format("HH:mm")} +
+
+ {app.paciente_nome} + Dr(a). {app.medico_nome} +
+
+ {app.status === "cancelled" ? ( + + ) : ( + + )} + {app.status !== "cancelled" && ( + + )} +
+
+ )) + ) : ( +
+

Nenhuma consulta agendada.

+
+ )} +
+
+
+
+
+ Realizado +
+
+ Confirmado +
+
+ Agendado +
+
+ Cancelado +
+
+
+
+

{currentDate.format("MMMM [de] YYYY")}

+
+ + + +
+
+
+ + + +
+
+
+ {weekDays.map((day) => ( +
+ {day} +
+ ))} + {dateGrid.map((day, index) => { + const dayString = day.format("YYYY-MM-DD"); + const appointmentsOnDay = + DictAgendamentosOrganizados[dayString] || []; + const filteredAppointments = appointmentsOnDay.filter( + (app) => + MedicoFiltrado.id === "vazio" || + app.doctor_id === MedicoFiltrado.id + ); + const cellClasses = `day-cell ${ + day.isSame(currentDate, "month") + ? "current-month" + : "other-month" + } ${day.isSame(dayjs(), "day") ? "today" : ""} ${ + day.isSame(selectedDay, "day") ? "selected" : "" + }`; + return ( +
handleDateClick(day)} + > + {day.format("D")} + {filteredAppointments.length > 0 && ( +
+ {filteredAppointments.length} +
+ )} +
+ ); + })} +
+
+
+ ) : ( +
+
+
+
+
+

Fila de Espera

+
+
+
+
+ {" "} + Filtros +
+
+ + setWaitlistSearch(e.target.value) + } + /> + + Digite o nome do paciente, CPF ou nome do médico + +
+
+
+ + Ordenar por: + + +
+
+
+
+ {filaEsperaFiltrada.length} DE{" "} + {filaEsperaData.length} SOLICITAÇÕES ENCONTRADAS +
+
+
+
+ + + + + + + + + + + + {filaEsperaPaginada.length > 0 ? ( + filaEsperaPaginada.map((item, index) => ( + + + + + + + + )) + ) : ( + + + + )} + +
Nome do PacienteCPFMédico SolicitadoData da SolicitaçãoAções
{item?.Infos?.paciente_nome}{item?.Infos?.paciente_cpf}{item?.Infos?.medico_nome} + {dayjs( + item.agendamento.scheduled_at + ).format("DD/MM/YYYY")} + + +
+
+ {showSpinner ? ( + + ) : ( + <> + +

+ Nenhuma solicitação encontrada. +

+ + )} +
+
+ {filaEsperaFiltrada.length > 0 && ( +
+
+ + Itens por página: + + +
+
+ + Página {waitPage} de {waitTotalPages} • + Mostrando {waitIndiceInicial + 1}- + {Math.min( + waitIndiceFinal, + filaEsperaFiltrada.length + )}{" "} + de {filaEsperaFiltrada.length} + + +
+
+ )} +
+
+
+
+
+
+ )} +
+
+ ) : ( + { + setPageConsulta(false); + fetchAppointments(); + }} + /> + )} + {showDeleteModal && } +
+ ); }; export default Agendamento; diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx index 13add57..6f86f00 100644 --- a/src/PagesMedico/DoctorRelatorioManager.jsx +++ b/src/PagesMedico/DoctorRelatorioManager.jsx @@ -46,7 +46,7 @@ const DoctorRelatorioManager = () => { if (authHeader) myHeaders.append('Authorization', authHeader); const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' }; - // Tenta descobrir o ID do usuário logado para aplicar filtro por médico + let userId = null; let userFullName = null; try { @@ -60,12 +60,12 @@ const DoctorRelatorioManager = () => { console.warn('Não foi possível obter UserInfos (pode não estar logado):', err); } - // Monta a URL com possíveis filtros preferenciais + const baseUrl = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*"; let data = []; if (userId) { - // 1) tenta por doctor_id + try { const res = await fetch(`${baseUrl}&doctor_id=eq.${userId}`, requestOptions); data = await res.json(); @@ -74,7 +74,7 @@ const DoctorRelatorioManager = () => { data = []; } - // 2) fallback para created_by (se vazio) + if ((!Array.isArray(data) || data.length === 0) && userId) { try { const res2 = await fetch(`${baseUrl}&created_by=eq.${userId}`, requestOptions); @@ -85,10 +85,9 @@ const DoctorRelatorioManager = () => { } } - // 3) fallback para requested_by com nome completo (se ainda vazio) if ((!Array.isArray(data) || data.length === 0) && userFullName) { try { - // encode para evitar problemas com espaços/caracteres especiais + const encodedName = encodeURIComponent(userFullName); const res3 = await fetch(`${baseUrl}&requested_by=eq.${encodedName}`, requestOptions); data = await res3.json(); @@ -99,7 +98,6 @@ const DoctorRelatorioManager = () => { } } - // Se não obteve userId ou nenhuma das tentativas acima retornou algo, busca tudo (comportamento anterior) if (!userId || (!Array.isArray(data) || data.length === 0)) { try { const resAll = await fetch(baseUrl, requestOptions); @@ -110,7 +108,7 @@ const DoctorRelatorioManager = () => { } } - // garante unicidade e ordenação por criação + const uniqueMap = new Map(); (Array.isArray(data) ? data : []).forEach(r => { if (r && r.id) uniqueMap.set(r.id, r); @@ -144,14 +142,14 @@ const DoctorRelatorioManager = () => { }; }, [authHeader]); - // Busca dados de pacientes e médicos baseados na lista final que aparece na tela + useEffect(() => { const fetchRelData = async () => { const pacientes = []; const medicos = []; for (let i = 0; i < relatoriosFinais.length; i++) { const rel = relatoriosFinais[i]; - // paciente + try { const pacienteRes = await GetPatientByID(rel.patient_id, authHeader); pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes); @@ -159,13 +157,12 @@ const DoctorRelatorioManager = () => { pacientes.push(null); } - // médico: prioriza campos com id (doctor_id ou created_by). Se tiver somente requested_by (nome), usa nome. try { if (rel.doctor_id) { const docRes = await GetDoctorByID(rel.doctor_id, authHeader); medicos.push(Array.isArray(docRes) ? docRes[0] : docRes); } else if (rel.created_by) { - // created_by costuma ser id + const docRes = await GetDoctorByID(rel.created_by, authHeader); medicos.push(Array.isArray(docRes) ? docRes[0] : docRes); } else if (rel.requested_by) { @@ -174,7 +171,6 @@ const DoctorRelatorioManager = () => { medicos.push({ full_name: '' }); } } catch (err) { - // fallback para requested_by se houver medicos.push({ full_name: rel.requested_by || '' }); } } @@ -190,7 +186,6 @@ const DoctorRelatorioManager = () => { }, [relatoriosFinais, authHeader]); const abrirModal = (relatorio, pageIndex) => { - // encontra índice global do relatório no array relatoriosFinais (para alinhar com pacientes/medicos) const globalIndex = relatoriosFinais.findIndex(r => r.id === relatorio.id); const indexToUse = globalIndex >= 0 ? globalIndex : (indiceInicial + pageIndex); setRelatorioModal(relatorio); @@ -198,7 +193,7 @@ const DoctorRelatorioManager = () => { setShowModal(true); }; - // Função para limpar filtros + const limparFiltros = () => { setTermoPesquisa(''); setFiltroExame(''); @@ -256,12 +251,11 @@ const DoctorRelatorioManager = () => { return (
{showModal && ( -
setShowModal(false)}> -
e.stopPropagation()}> +
+
-
+
Relatório de {pacientesComRelatorios[modalIndex]?.full_name}
-
@@ -290,11 +284,11 @@ const DoctorRelatorioManager = () => {
-
+
-
diff --git a/src/PagesMedico/NovoRelatorioAudio.jsx b/src/PagesMedico/NovoRelatorioAudio.jsx new file mode 100644 index 0000000..6012435 --- /dev/null +++ b/src/PagesMedico/NovoRelatorioAudio.jsx @@ -0,0 +1,215 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import html2pdf from 'html2pdf.js'; +import './styleMedico/NovoRelatorioAudio.css'; + +const NovoRelatorioAudio = () => { + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + + const [formData, setFormData] = useState({ + paciente_nome: '', + data_exame: new Date().toISOString().split('T')[0], + exam: '', + diagnostico: '', + conclusao: '', + medico_nome: 'Dr.______________________' + }); + + const handleAudioUpload = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + setLoading(true); + const data = new FormData(); + data.append('audio', file); + + try { + const response = await fetch('http://localhost:3001/api/transcrever-relatorio', { + method: 'POST', + body: data, + }); + + if (!response.ok) throw new Error('Erro na comunicação com a API'); + + const result = await response.json(); + + setFormData(prev => ({ + ...prev, + exam: result.exam || prev.exam, + diagnostico: result.diagnostico || prev.diagnostico, + conclusao: result.conclusao || prev.conclusao + })); + + } catch (error) { + console.error(error); + alert("Erro: Verifique se o backend (porta 3001) está rodando."); + } finally { + setLoading(false); + e.target.value = null; + } + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handlePrintPDF = () => { + const element = document.getElementById('documento-final'); + const opt = { + margin: 0, + filename: `Laudo_${formData.paciente_nome || 'SemNome'}.pdf`, + image: { type: 'jpeg', quality: 0.98 }, + html2canvas: { scale: 2 }, + jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } + }; + html2pdf().set(opt).from(element).save(); + }; + + return ( +
+ +
+
+

AI Report

+

Gerador de laudos automatizado

+
+ +
+ {loading ? ( +
+ ) : ( + <> + + + + )} +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+
+ +
+
+

CLÍNICA RISE UP

+

Excelência em Diagnóstico por Imagem

+
+
+

{formData.medico_nome}

+

CRM/SP 123456

+

{new Date().toLocaleDateString()}

+
+
+ +
+ + + + + + + + + + + + + + + +
Paciente:{formData.paciente_nome || '__________________________'}
Exame:{formData.exam || '__________________________'}
Data:{new Date(formData.data_exame).toLocaleDateString('pt-BR')}
+
+ +
+

Relatório Médico

+ +

Análise:

+

+ {formData.diagnostico || O diagnóstico aparecerá aqui após o processamento do áudio...} +

+ +

Conclusão:

+

+ {formData.conclusao} +

+
+ +
+

________________________________________

+

{formData.medico_nome}

+

Assinatura Eletrônica

+
+ +
+
+
+ ); +}; + +export default NovoRelatorioAudio; \ No newline at end of file diff --git a/src/PagesMedico/styleMedico/Agendamento.css b/src/PagesMedico/styleMedico/Agendamento.css index ffa5552..5f01d68 100644 --- a/src/PagesMedico/styleMedico/Agendamento.css +++ b/src/PagesMedico/styleMedico/Agendamento.css @@ -6,18 +6,58 @@ /* --- Posiciona a barra de busca corretamente --- */ .busca-atendimento { display: flex; - align-items: center; /* Alinha os itens verticalmente */ - margin-top: 20px; /* Espaço acima da barra de busca */ - padding: 0 10px; /* Adiciona um padding lateral para alinhar com o resto */ + align-items: center; + margin-top: 20px; + padding: 0 10px; gap: 15px; } .busca-atendimento > div:first-child { - width: 400px; /* Define um tamanho para a barra de pesquisa */ + width: 400px; display: flex; align-items: center; } +@media (max-width: 768px) { + .busca-atendimento { + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .busca-atendimento > div:first-child { + width: 100%; + } + + .btns-e-legenda-container { + flex-direction: column; + align-items: flex-start; + gap: 10px; + } + + .legenda-tabela { + flex-wrap: wrap; + gap: 8px; + } +} + +@media (max-width: 576px) { + .busca-atendimento { + padding: 0 5px; + } + + .btns-e-legenda-container { + padding: 0 5px; + } + + .btn-selecionar-tabeladia, + .btn-selecionar-tabelasemana, + .btn-selecionar-tabelames { + padding: 6px 8px; + font-size: medium; + } +} + .busca-atendimento input { margin-left: 8px; border-radius: 8px; @@ -140,6 +180,7 @@ border-bottom: 3px solid transparent; padding: 8px; border-radius: 10px 10px 0px 0px; + color: #fff; font-weight: bold; cursor: pointer; } diff --git a/src/PagesMedico/styleMedico/NovoRelatorioAudio.css b/src/PagesMedico/styleMedico/NovoRelatorioAudio.css new file mode 100644 index 0000000..591eef3 --- /dev/null +++ b/src/PagesMedico/styleMedico/NovoRelatorioAudio.css @@ -0,0 +1,119 @@ +/* Container que ocupa toda a altura da tela (menos o header do site) */ +.ai-editor-container { + display: flex; + min-height: calc(100vh - 80px); /* Ajuste conforme seu header */ + background-color: #e9ecef; /* Cor de "Mesa" */ + overflow: hidden; +} + +/* --- LADO ESQUERDO: PAINEL DE CONTROLE --- */ +.editor-sidebar { + width: 400px; + background-color: #212529; /* Dark Mode para o painel */ + color: #fff; + padding: 20px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 15px; + box-shadow: 4px 0 10px rgba(0,0,0,0.1); +} + +.editor-sidebar h4 { + color: #0dcaf0; /* Ciano para destaque */ + margin-bottom: 20px; + font-weight: 700; +} + +.editor-sidebar label { + font-size: 0.85rem; + color: #adb5bd; + margin-bottom: 5px; +} + +/* Estilo customizado para inputs no fundo escuro */ +.dark-input { + background-color: #343a40; + border: 1px solid #495057; + color: #fff; + border-radius: 6px; +} +.dark-input:focus { + background-color: #3b4248; + color: #fff; + border-color: #0dcaf0; + box-shadow: none; +} + +/* Área de Upload Destacada */ +.ai-upload-box { + border: 2px dashed #0dcaf0; + border-radius: 10px; + padding: 20px; + text-align: center; + background: rgba(13, 202, 240, 0.1); + transition: 0.3s; + cursor: pointer; +} +.ai-upload-box:hover { + background: rgba(13, 202, 240, 0.2); +} + +/* --- LADO DIREITO: PREVIEW A4 --- */ +.preview-area { + flex: 1; + display: flex; + justify-content: center; + align-items: flex-start; + padding: 40px; + overflow-y: auto; +} + +.paper-a4 { + width: 210mm; + min-height: 297mm; + background: white; + padding: 25mm; + box-shadow: 0 0 20px rgba(0,0,0,0.15); + color: #000; + font-family: 'Georgia', serif; /* Fonte mais séria para o documento */ + font-size: 12pt; + line-height: 1.6; +} + +/* Responsividade: Em celular vira coluna única */ +@media (max-width: 900px) { + .ai-editor-container { + flex-direction: column; + min-height: auto; + } + .editor-sidebar { + width: 100%; + height: auto; + box-shadow: 0 4px 10px rgba(0,0,0,0.1); + } + .preview-area { + padding: 20px; + } + .paper-a4 { + width: 100%; + min-height: auto; + padding: 15mm; + box-shadow: none; + } +} + +@media (max-width: 576px) { + .ai-editor-container { + padding: 0; + } + .editor-sidebar { + padding: 15px; + } + .preview-area { + padding: 10px; + } + .paper-a4 { + padding: 10mm; + } +} \ No newline at end of file diff --git a/src/PagesPaciente/style.css b/src/PagesPaciente/style.css index 2d0b21f..e675e55 100644 --- a/src/PagesPaciente/style.css +++ b/src/PagesPaciente/style.css @@ -1,14 +1,71 @@ /* Estilo geral do card para agrupar e dar um formato */ .card-consulta { - background-color: #007bff; /* Um tom de azul padrão */ - display: flex; /* Para colocar horário e info lado a lado */ - border-radius: 10px; /* Cantos arredondados */ - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Sombra suave */ - overflow: hidden; /* Garante que o fundo azul não 'vaze' */ - /* width: 280px; /* Largura de exemplo */ + background-color: #007bff; + display: flex; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + overflow: hidden; margin: 20px; - font-family: Arial, sans-serif; /* Fonte legível */ + font-family: Arial, sans-serif; +} + +@media (max-width: 768px) { + .card-consulta { + flex-direction: column; + margin: 10px; + } + + .horario-container { + border-right: none; + border-bottom: 1px solid #0056b3; + padding: 10px 15px; + } + + .horario { + font-size: 1.8em; + } + + .info-container { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + padding: 15px; + } + + .actions-container { + opacity: 1; + visibility: visible; + background: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border: none; + box-shadow: none; + margin-left: 0; + padding: 0; + justify-content: flex-start; + margin-top: 10px; + } + + .card-consulta:hover .actions-container { + transform: none; + } +} + +@media (max-width: 576px) { + .horario { + font-size: 1.5em; + } + + .informacao { + font-size: 1em; + } + + .btn-edit-custom-style, + .btn-delete-custom-style { + padding: 6px 10px; + font-size: 0.8rem; + } } /* 1. Estilo para o Horário (Fundo Azul e Texto Branco/Grande) */ diff --git a/src/components/AgendarConsulta/style/card-consulta.css b/src/components/AgendarConsulta/style/card-consulta.css index 34d8dea..285a558 100644 --- a/src/components/AgendarConsulta/style/card-consulta.css +++ b/src/components/AgendarConsulta/style/card-consulta.css @@ -1,3 +1,10 @@ +@media (max-width: 768px) { + .container-cardconsulta { + padding-right: 80px; /* Espaço para os botões */ + position: relative; + } +} + .actions-container { display: flex; gap: 8px; @@ -84,4 +91,50 @@ .tabelamensal .container-cardconsulta{ width: 24rem; +} + +@media (max-width: 768px) { + .actions-container { + opacity: 1; + visibility: visible; + background: none; + backdrop-filter: none; + -webkit-backdrop-filter: none; + border: none; + box-shadow: none; + margin-left: 0; + padding: 0; + justify-content: flex-end; + position: absolute; + top: 10px; + right: 10px; + } + + .container-cardconsulta:hover .actions-container { + transform: none; + } + + .tabelamensal .container-cardconsulta { + width: 100%; + max-width: 100%; + } +} + +@media (max-width: 576px) { + .container-cardconsulta { + padding-right: 10px; /* Remove o padding extra para telas muito pequenas */ + } + + .actions-container { + position: static; + margin-top: 10px; + justify-content: flex-start; + } + + .btn-edit-custom-style, + .btn-delete-custom-style, + .btn-confirm-style { + padding: 6px 10px; + font-size: 0.8rem; + } } \ No newline at end of file diff --git a/src/components/AgendarConsulta/style/formagendamentos.css b/src/components/AgendarConsulta/style/formagendamentos.css index 3fcf61f..f83515c 100644 --- a/src/components/AgendarConsulta/style/formagendamentos.css +++ b/src/components/AgendarConsulta/style/formagendamentos.css @@ -183,6 +183,67 @@ textarea{ gap: 16px; } +@media (max-width: 768px) { + .campos-informacoes-paciente, + .campo-informacoes-atendimento { + flex-direction: column; + gap: 10px; + } + + #informacoes-atendimento-segunda-linha { + flex-direction: column; + gap: 10px; + } + + #informacoes-atendimento-segunda-linha-esquerda select[name="unidade"], + input[type="time"], + select[name=solicitante], + .campo-de-input { + width: 100% !important; + max-width: 100%; + } + + .tipo_atendimento { + margin-left: 0; + } + + .linha { + flex-direction: column; + align-items: stretch; + gap: 10px; + } + + .sessao-contador { + width: 100px; + } +} + +@media (max-width: 576px) { + .form-container { + padding: 15px; + } + + .form-title { + font-size: 22px; + } + + .form-agendamento input, + .form-agendamento select, + .form-agendamento textarea { + font-size: 13px; + } + + .form-actions { + flex-direction: column; + gap: 10px; + } + + .btn-primary, + .btn-cancel { + width: 100%; + } +} + .campo-de-input { flex: 1; display: flex; diff --git a/src/components/FormRelatorio.jsx b/src/components/FormRelatorio.jsx index 7c04a49..fb7a01e 100644 --- a/src/components/FormRelatorio.jsx +++ b/src/components/FormRelatorio.jsx @@ -1,55 +1,97 @@ -import React from 'react' +import React, { useState } from 'react' import '../PagesMedico/styleMedico/FormNovoRelatorio.css' -import { useState } from 'react' import { useNavigate } from 'react-router-dom' import { useAuth } from '../components/utils/AuthProvider' import { GetPatientByCPF } from '../components/utils/Functions-Endpoints/Patient' import { FormatCPF } from '../components/utils/Formatar/Format' import html2pdf from 'html2pdf.js' -const FormRelatorio = ({onSave, DictInfo, setDictInfo }) => { - const {getAuthorizationHeader} = useAuth() +const FormRelatorio = ({ onSave, DictInfo, setDictInfo }) => { + const { getAuthorizationHeader } = useAuth() let authHeader = getAuthorizationHeader() - const navigate= useNavigate() - + const navigate = useNavigate() + const [showModal, setShowModal] = useState(false) + + // --- NOVO: Estado para controlar o loading da transcrição --- + const [isTranscribing, setIsTranscribing] = useState(false); + + // --- NOVA FUNÇÃO: Envia o áudio e preenche o formulário --- + const handleAudioUpload = async (e) => { + const file = e.target.files[0]; + if (!file) return; + + setIsTranscribing(true); // Ativa o spinner + + const formData = new FormData(); + formData.append('audio', file); // 'audio' deve ser o nome esperado no backend + + try { + // ⚠️ ATENÇÃO: Substitua essa URL pela rota do seu backend que criamos + const response = await fetch('http://localhost:3001/api/transcrever-relatorio', { + method: 'POST', + body: formData, + // headers: { 'Authorization': authHeader } // Descomente se seu backend precisar de token + }); + + if (!response.ok) throw new Error("Falha na transcrição"); + + const data = await response.json(); + + // Atualiza o DictInfo com os dados vindos da IA + setDictInfo((prev) => ({ + ...prev, + exam: data.exam || prev.exam, // Preenche se a IA achou, senão mantém o antigo + diagnostico: data.diagnostico || prev.diagnostico, + conclusao: data.conclusao || prev.conclusao + })); + + } catch (error) { + console.error("Erro no upload de áudio:", error); + alert("Não foi possível gerar o relatório por áudio. Verifique o backend."); + } finally { + setIsTranscribing(false); // Desativa o spinner + e.target.value = null; // Limpa o input para permitir enviar o mesmo arquivo novamente se quiser + } + }; + // ----------------------------------------------------------- const BaixarPDFdoRelatorio = () => { const elemento = document.getElementById("folhaA4"); // tua div do relatório - const opt = { - margin: 0, - filename: `relatorio_${DictInfo?.paciente_nome || "paciente"}.pdf`, - html2canvas: { scale: 2 }, - jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, - }; + const opt = { + margin: 0, + filename: `relatorio_${DictInfo?.paciente_nome || "paciente"}.pdf`, + html2canvas: { scale: 2 }, + jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, + }; - html2pdf().set(opt).from(elemento).save(); + html2pdf().set(opt).from(elemento).save(); } const handleChange = (e) => { const { name, value } = e.target; console.log(name, value) - if(name === 'paciente_cpf') { - const formattedCPF = FormatCPF(value); - setDictInfo((prev) => ({ ...prev, [name]: formattedCPF })); + if (name === 'paciente_cpf') { + const formattedCPF = FormatCPF(value); + setDictInfo((prev) => ({ ...prev, [name]: formattedCPF })); - const fetchPatient = async () => { - const patientData = await GetPatientByCPF(formattedCPF, authHeader); - if (patientData) { - setDictInfo((prev) => ({ - ...prev, - paciente_cpf:value, - paciente_nome: patientData.full_name, - paciente_id: patientData.id - })); + const fetchPatient = async () => { + const patientData = await GetPatientByCPF(formattedCPF, authHeader); + if (patientData) { + setDictInfo((prev) => ({ + ...prev, + paciente_cpf: value, + paciente_nome: patientData.full_name, + paciente_id: patientData.id + })); + } + + }; + if (formattedCPF.length === 14) { + fetchPatient(); } - - }; - if(formattedCPF.length === 14){ - fetchPatient(); - } - }else{ - setDictInfo((prev) => ({ ...prev, [name]: value })); + } else { + setDictInfo((prev) => ({ ...prev, [name]: value })); } } @@ -57,145 +99,164 @@ const FormRelatorio = ({onSave, DictInfo, setDictInfo }) => { e.preventDefault(); console.log(DictInfo) setShowModal(true) - - -onSave({ - "patient_id": DictInfo.paciente_id, - - "exam": DictInfo.exam, - "diagnosis": DictInfo.diagnosis, - "conclusion": DictInfo.conclusao, - "status": "draft", - "requested_by": DictInfo.requested_by, - - "hide_date": false, - "hide_signature": false, -}); + onSave({ + "patient_id": DictInfo.paciente_id, + "exam": DictInfo.exam, + "diagnosis": DictInfo.diagnostico, // Garanta que o backend espera 'diagnosis' mas seu state usa 'diagnostico' + "conclusion": DictInfo.conclusao, + "status": "draft", + "requested_by": DictInfo.requested_by, + "hide_date": false, + "hide_signature": false, + }); } - return ( -
- {showModal &&( -
-
-
-
-
Relatório criado com sucesso
- + return ( +
+ {showModal && ( +
+
+
+
+
Relatório criado com sucesso
+ +
+
+

Você também pode baixa-lo agora em pdf

+
+
+ + + +
+
+
-
-

Você também pode baixa-lo agora em pdf

-
-
- + )} - -
-
-
-
- )} - -
- -
-
- -
- - -
- -
- - -
- - -
- - -
- -
- - -
- - -
- - -
- -
- -
-
- - -
- -
- - -
-
- - +
- -
+ {/* --- ÁREA DE UPLOAD DE ÁUDIO (INSERIDA AQUI) --- */} +
+ +
+ {isTranscribing ? ( +
+
+ A IA está gerando o relatório... aguarde. +
+ ) : ( + + )} +
+ Envie um áudio ditando o exame, diagnóstico e conclusão. +
+ {/* ----------------------------------------------- */} -

Modelo do relatório

-
+
+
-
-

Clinica Rise up

-

Dr {DictInfo.requested_by} - CRM/SP 123456

-

Avenida - (79) 9 4444-4444

-
+
+ + +
-
-

Paciente: {DictInfo?.paciente_nome}

-

Data de nascimento:

+
+ + +
-

Data do exame: {DictInfo.data_exam}

-

Exame: {DictInfo.exam}

+
+ + +
-

Diagnostico: {DictInfo.diagnostico}

+
+ + +
-

Conclusão: {DictInfo.conclusao}

- -
-
-

Dr {DictInfo.requested_by}

-

Emitido em: 0

-
+
+ + +
-
- -
- ) +
+ +
+
+ + +
+ +
+ + +
+
+ + + + +
+ +

Modelo do relatório

+
+ +
+

Clinica Rise up

+

Dr {DictInfo.requested_by} - CRM/SP 123456

+

Avenida - (79) 9 4444-4444

+
+ +
+

Paciente: {DictInfo?.paciente_nome}

+

Data de nascimento:

+ + {/* Corrigi de data_exam para data_exame para bater com o state */} +

Data do exame: {DictInfo.data_exame}

+ +

Exame: {DictInfo.exam}

+ +

Diagnostico: {DictInfo.diagnostico}

+ +

Conclusão: {DictInfo.conclusao}

+ +
+ +
+

Dr {DictInfo.requested_by}

+

Emitido em: {new Date().toLocaleDateString()}

+
+ +
+ +
+ ) } export default FormRelatorio \ No newline at end of file diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css index 0a4737d..292356f 100644 --- a/src/components/Header/Header.css +++ b/src/components/Header/Header.css @@ -223,6 +223,8 @@ z-index: 2001; margin-top: 80px; margin-right: 20px; + /* Adicionado para responsividade */ + max-width: 90vw; } .suporte-card { @@ -309,6 +311,8 @@ z-index: 3001; margin-top: 80px; margin-right: 20px; + /* Adicionado para responsividade */ + max-width: 90vw; } .chat-online { @@ -478,6 +482,7 @@ .suporte-card-container, .chat-container { + margin-top: 60px; margin-right: 10px; margin-left: 10px; } @@ -489,28 +494,101 @@ } } -/* permite que cliques "passem" através do header (exceto para os elementos interativos) */ -.header-container { - pointer-events: none; /* header não captura cliques */ +@media (max-width: 576px) { + .header-container { + padding: 8px 10px; + } + + .right-corner-elements { + gap: 10px; + } + + .profile-picture-container { + width: 35px; + height: 35px; + } + + .phone-icon-container { + font-size: 20px; + } + + .suporte-card-container, + .chat-container { + margin-top: 50px; + margin-right: 5px; + margin-left: 5px; + } + + .suporte-card { + padding: 1rem; + } + + .chat-online { + width: calc(100vw - 10px); + height: 80vh; /* Limita a altura para telas pequenas */ + } + + .chat-input { + padding: 0.75rem; + } + + .chat-campo { + padding: 0.5rem; + } + + .chat-enviar { + padding: 0.5rem 1rem; + } } -/* mas permite que os controles no canto (telefone e profile) continuem clicáveis */ +@media (max-width: 768px) { + .header-container { + padding: 10px 15px; + } + + .right-corner-elements { + gap: 15px; + } + + .profile-picture-container { + width: 40px; + height: 40px; + } + + .suporte-card-container, + .chat-container { + margin-right: 10px; + margin-left: 10px; + } + + .suporte-card, + .chat-online { + width: calc(100vw - 20px); + max-width: none; + } +} + +.header-container { + pointer-events: none; +} + + .phone-icon-container, .profile-section { pointer-events: auto; } -/* Garantir pointer-events nos elementos do header e overlays criados por portal */ + .header-container { pointer-events: auto; } .phone-icon-container, .profile-section { pointer-events: auto; } -/* Força que os overlays criados por portal fiquem por cima */ + .logout-modal-overlay, .suporte-card-overlay, .chat-overlay { z-index: 110000 !important; pointer-events: auto !important; } -/* Pequeno ajuste visual dos botões do modal (pode se misturar com seu CSS atual) */ + .logout-cancel-button { padding: 10px 18px; border-radius: 8px; diff --git a/src/components/utils/supabaseClient.js b/src/components/utils/supabaseClient.js deleted file mode 100644 index 1590360..0000000 --- a/src/components/utils/supabaseClient.js +++ /dev/null @@ -1,7 +0,0 @@ -// src/utils/supabaseClient.js -import { createClient } from "@supabase/supabase-js"; -import API_KEY from "./apiKeys"; - -const SUPABASE_URL = "https://yuanqfswhberkoevtmfr.supabase.co"; - -export const supabase = createClient(SUPABASE_URL, API_KEY); diff --git a/src/data/sidebar-items-adm.json b/src/data/sidebar-items-adm.json index 5b4c554..1eb3da2 100644 --- a/src/data/sidebar-items-adm.json +++ b/src/data/sidebar-items-adm.json @@ -25,5 +25,10 @@ "name": "Painel Administrativo", "icon": "file-bar-graph-fill", "url": "/admin/painel" + }, + { + "name": "Gestão de Usuários", + "icon": "people-fill", + "url": "/admin/gestao" } ] diff --git a/src/data/sidebar-items-medico.json b/src/data/sidebar-items-medico.json index 0ededdb..b023323 100644 --- a/src/data/sidebar-items-medico.json +++ b/src/data/sidebar-items-medico.json @@ -5,17 +5,26 @@ "url": "/medico/agendamento" }, + + + { + "name": "Relatório por Áudio", + "icon": "file-earmark-plus-fill", + "url": "/medico/novo-relatorio-audio" + }, + { "name": "Relatórios", "icon": "file-earmark-text-fill", "url": "/medico/relatorios" }, + { "name": "Chat com pacientes", "icon": "chat-dots-fill", "url": "/medico/chat" } - + ] diff --git a/src/index.css b/src/index.css index bd5bd6d..43cf021 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,19 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} +*, +*::before, +*::after { + box-sizing: border-box; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} diff --git a/src/pages/DisponibilidadesDoctorPage.jsx b/src/pages/DisponibilidadesDoctorPage.jsx index ef488d8..5f79a67 100644 --- a/src/pages/DisponibilidadesDoctorPage.jsx +++ b/src/pages/DisponibilidadesDoctorPage.jsx @@ -38,6 +38,9 @@ const DisponibilidadesDoctorPage = () => { const [expandedDoctors, setExpandedDoctors] = useState({}); const [showSuggestions, setShowSuggestions] = useState(false); const [availabilityEdit, setAvailabilityEdit] = useState([]); + // Add the missing state variables + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [selectedDisponibilidadeId, setSelectedDisponibilidadeId] = useState(null); const getHeaders = () => { const myHeaders = new Headers(); @@ -170,8 +173,24 @@ const DisponibilidadesDoctorPage = () => { if (!window.confirm("Deseja realmente excluir esta disponibilidade?")) return; try { const res = await fetch(`${ENDPOINT}?id=eq.${id}`, { method: "DELETE", headers: getHeaders() }); - if (res.ok) setDisponibilidades((prev) => prev.filter((d) => d.id !== id)); - } catch (error) {} + if (res.ok) { + setDisponibilidades((prev) => prev.filter((d) => d.id !== id)); + setShowDeleteModal(false); + setSelectedDisponibilidadeId(null); + } + } catch (error) { + console.error("Erro ao excluir disponibilidade:", error); + } + }; + + const handleOpenDeleteModal = (id) => { + setSelectedDisponibilidadeId(id); + setShowDeleteModal(true); + }; + + const handleCloseDeleteModal = () => { + setShowDeleteModal(false); + setSelectedDisponibilidadeId(null); }; const disponibilidadesAgrupadas = useMemo(() => { @@ -420,7 +439,16 @@ const DisponibilidadesDoctorPage = () => { {getStatusText(disp)} - {!disp.is_empty && } + + {!disp.is_empty && ( + + )} + ))} @@ -435,6 +463,51 @@ const DisponibilidadesDoctorPage = () => { )}
+ + {showDeleteModal && ( +
+
+
+
+
+ Confirmação de Exclusão +
+
+ +
+

+ Tem certeza que deseja excluir esta disponibilidade? +

+
+ +
+ + + +
+
+
+
+ )}
); }; diff --git a/src/pages/DoctorTable.jsx b/src/pages/DoctorTable.jsx index a0f3650..e5becd7 100644 --- a/src/pages/DoctorTable.jsx +++ b/src/pages/DoctorTable.jsx @@ -27,9 +27,8 @@ function TableDoctor({setDictInfo}) { const [showDeleteModal, setShowDeleteModal] = useState(false); const [selectedDoctorId, setSelectedDoctorId] = useState(null); - // Ordenação rápida - const [sortKey, setSortKey] = useState(null); // 'nome' | 'idade' | null - const [sortDir, setSortDir] = useState('asc'); // 'asc' | 'desc' + const [sortKey, setSortKey] = useState(null); + const [sortDir, setSortDir] = useState('asc'); const limparFiltros = () => { setSearch(""); @@ -147,7 +146,6 @@ function TableDoctor({setDictInfo}) { return resultado; }) : []; - const applySorting = (arr) => { if (!Array.isArray(arr) || !sortKey) return arr; const copy = [...arr]; @@ -277,7 +275,7 @@ function TableDoctor({setDictInfo}) {
- {/* Ordenação rápida */} +
Ordenar por: @@ -481,7 +479,6 @@ function TableDoctor({setDictInfo}) { - {/* Paginação */} {medicosFiltrados.length > 0 && (
@@ -554,15 +551,10 @@ function TableDoctor({setDictInfo}) { >
-
+
Confirmação de Exclusão
-
diff --git a/src/pages/EditPage.jsx b/src/pages/EditPage.jsx index 0f9bb18..dac83d8 100644 --- a/src/pages/EditPage.jsx +++ b/src/pages/EditPage.jsx @@ -9,6 +9,7 @@ import { useAuth } from '../components/utils/AuthProvider' const EditPage = ({DictInfo}) => { const navigate = useNavigate() const [PatientToPUT, setPatientPUT] = useState({}) + const [showSuccessModal, setShowSuccessModal] = useState(false) const { getAuthorizationHeader, isAuthenticated } = useAuth(); @@ -37,9 +38,15 @@ const HandlePutPatient = async () => { }; fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients?id=eq.${PatientToPUT.id}`,requestOptions) - .then(response => console.log(response)) + .then(response => { + console.log(response) + if (response.ok) { + setShowSuccessModal(true) + } + return response + }) .then(result => console.log(result)) - .catch(console.log("erro")) + .catch(error => console.log("erro", error)) }; @@ -52,6 +59,46 @@ const HandlePutPatient = async () => { formData={PatientToPUT} setFormData={setPatientPUT} /> + + {showSuccessModal && ( +
+
+
+
+
+ Paciente Editado +
+
+ +
+

+ Paciente editado com sucesso! +

+
+ +
+ +
+
+
+
+ )}
) diff --git a/src/pages/ExcecoesDisponibilidade.jsx b/src/pages/ExcecoesDisponibilidade.jsx index db2e034..e4f834b 100644 --- a/src/pages/ExcecoesDisponibilidade.jsx +++ b/src/pages/ExcecoesDisponibilidade.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; import dayjs from 'dayjs'; import 'dayjs/locale/pt-br'; import weekday from 'dayjs/plugin/weekday'; @@ -23,12 +24,10 @@ const getDateRange = (date, view) => { toDate = startDayjs.format('YYYY-MM-DD'); titleRange = startDayjs.format('DD/MM/YYYY'); } else if (view === 'semanal') { - // Padrão Dayjs: Sunday=0, Monday=1. - // startOf('week') pode ser Domingo ou Segunda, dependendo do locale. - // Se precisar forçar a Segunda-feira: + let weekStart = startDayjs.startOf('week'); - if (weekStart.day() !== 1) { // Se não for segunda-feira (1), ajusta - weekStart = startDayjs.weekday(1); // Vai para a segunda-feira desta semana + if (weekStart.day() !== 1) { + weekStart = startDayjs.weekday(1); } const weekEnd = weekStart.add(6, 'day'); @@ -52,12 +51,21 @@ const getDateRange = (date, view) => { const ExcecoesDisponibilidade = () => { const { getAuthorizationHeader } = useAuth(); + const navigate = useNavigate(); const [pageNovaExcecao, setPageNovaExcecao] = useState(false); const [excecoes, setExcecoes] = useState([]); const [loading, setLoading] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [selectedExceptionId, setSelectedExceptionId] = useState(null); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [successMessage, setSuccessMessage] = useState(''); const [filtroMedicoId, setFiltroMedicoId] = useState(''); const [filtroData, setFiltroData] = useState(dayjs().format('YYYY-MM-DD')); + const [listaDeMedicos, setListaDeMedicos] = useState([]); + const [searchTermDoctor, setSearchTermDoctor] = useState(''); + const [filteredDoctors, setFilteredDoctors] = useState([]); + const [selectedDoctor, setSelectedDoctor] = useState(null); const [visualizacao, setVisualizacao] = useState('diario'); @@ -123,8 +131,51 @@ const ExcecoesDisponibilidade = () => { fetchExcecoes(fromDate, toDate, filtroMedicoId); }, [fetchExcecoes, filtroMedicoId, fromDate, toDate]); + useEffect(() => { + const fetchDoctors = async () => { + const myHeaders = new Headers(); + const authHeader = resolveAuthHeader(); + if (authHeader) myHeaders.append("Authorization", authHeader); + if (API_KEY) myHeaders.append("apikey", API_KEY); + + try { + const response = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?select=id,full_name', { + method: 'GET', + headers: myHeaders + }); + if (response.ok) { + const doctors = await response.json(); + setListaDeMedicos(doctors); + } + } catch (error) { + console.error('Erro ao buscar médicos:', error); + } + }; + fetchDoctors(); + }, []); + + const handleSearchDoctors = (term) => { + setSearchTermDoctor(term); + if (term.trim() === '') { + setFilteredDoctors([]); + return; + } + const filtered = listaDeMedicos.filter(doc => + doc.full_name.toLowerCase().includes(term.toLowerCase()) + ); + setFilteredDoctors(filtered); + }; + + const limparFiltros = () => { + setSearchTermDoctor(''); + setFilteredDoctors([]); + setSelectedDoctor(null); + setFiltroMedicoId(''); + setFiltroData(dayjs().format('YYYY-MM-DD')); + setVisualizacao('diario'); + }; + const deleteExcecao = async (id) => { - if (!window.confirm("Confirma exclusão desta exceção?")) return; const myHeaders = new Headers(); const authHeader = resolveAuthHeader(); if (authHeader) myHeaders.append("Authorization", authHeader); @@ -139,6 +190,9 @@ const ExcecoesDisponibilidade = () => { }); if (res.ok) { setExcecoes(prev => prev.filter(x => x.id !== id)); + setShowDeleteModal(false); + setSuccessMessage('Exceção excluída com sucesso!'); + setShowSuccessModal(true); } else { const text = await res.text(); console.error('Erro ao deletar exceção', res.status, text); @@ -163,7 +217,7 @@ const ExcecoesDisponibilidade = () => { return (
- {/* Título e Botão de Criação */} +

Gerenciar Exceções de Disponibilidade

-
+
+
+ + Filtros +
- {/* Filtros de Médico e Data */} -
-
- +
+
+ setFiltroMedicoId(e.target.value)} + className="form-control" + placeholder="Digite o nome do médico..." + value={searchTermDoctor} + onChange={(e) => handleSearchDoctors(e.target.value)} /> + Filtre as exceções por médico + {searchTermDoctor && filteredDoctors.length > 0 && ( +
+ {filteredDoctors.map((doc) => ( + + ))} +
+ )}
-
- - + + setFiltroData(e.target.value)} /> + Selecione a data base para visualização
- {/* Botões de Visualização (Dia/Semana/Mês) */} +
+
+ {selectedDoctor && ( + + + {selectedDoctor.full_name} + + )} +
+ {excecoes.length} DE {excecoes.length} EXCEÇÕES ENCONTRADAS +
+
+ + +
+
+ +
+ +
- {/* Tabela de Exceções (Título usa o titleRange calculado) */} +

Exceções em {titleRange} ({excecoes.length})

@@ -264,7 +368,10 @@ const ExcecoesDisponibilidade = () => { @@ -278,6 +385,89 @@ const ExcecoesDisponibilidade = () => {
+ + {showDeleteModal && ( +
+
+
+
+
+ Confirmação de Exclusão +
+
+ +
+

+ Tem certeza que deseja excluir esta exceção? +

+
+ +
+ + + +
+
+
+
+ )} + + + {showSuccessModal && ( +
+
+
+
+
+ Sucesso +
+
+ +
+

+ {successMessage} +

+
+ +
+ +
+
+
+
+ )}
); } diff --git a/src/pages/FinanceiroDashboard.jsx b/src/pages/FinanceiroDashboard.jsx index b3768ce..a2c868d 100644 --- a/src/pages/FinanceiroDashboard.jsx +++ b/src/pages/FinanceiroDashboard.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; -import './style/FinanceiroDashboard.css'; +import "./style/FinanceiroDashboard.css"; const CONVENIOS_LIST = [ "Particular", @@ -8,72 +8,77 @@ const CONVENIOS_LIST = [ "SulAmérica", "Unimed", "Cassio", - "Outro" + "Outro", ]; function CurrencyInput({ value, onChange, label, id }) { const formattedValue = useMemo(() => { let numericValue = Number(value) || 0; - + let stringValue = String(numericValue); while (stringValue.length < 3) { - stringValue = '0' + stringValue; + stringValue = "0" + stringValue; } const integerPart = stringValue.slice(0, -2); const decimalPart = stringValue.slice(-2); - - const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.'); + + const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, "."); return `R$ ${formattedInteger},${decimalPart}`; }, [value]); - const handleKeyDown = useCallback((e) => { - const key = e.key; + const handleKeyDown = useCallback( + (e) => { + const key = e.key; - if (['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(key)) { - if (key === 'Backspace' || key === 'Delete') { + if ( + ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab"].includes(key) + ) { + if (key === "Backspace" || key === "Delete") { e.preventDefault(); const numericValue = value || 0; let newValueString = String(numericValue); if (newValueString.length <= 1) { - onChange(0); + onChange(0); } else { - const newNumericValue = parseInt(newValueString.slice(0, -1)) || 0; - onChange(newNumericValue); + const newNumericValue = parseInt(newValueString.slice(0, -1)) || 0; + onChange(newNumericValue); } + } + return; + } + + if (!/^\d$/.test(key)) { + e.preventDefault(); + return; } - return; - } - if (!/^\d$/.test(key)) { e.preventDefault(); - return; - } - - e.preventDefault(); - - const digit = key; - const numericValue = value || 0; - let newValueString = String(numericValue) + digit; - - if (newValueString.length > 10) return; + const digit = key; + const numericValue = value || 0; - const newNumericValue = parseInt(newValueString); + let newValueString = String(numericValue) + digit; - onChange(newNumericValue); - }, [value, onChange]); + if (newValueString.length > 10) return; + + const newNumericValue = parseInt(newValueString); + + onChange(newNumericValue); + }, + [value, onChange] + ); return (
- {}} + className="input-field currency-input" + type="text" + value={formattedValue} + onChange={() => {}} onKeyDown={handleKeyDown} placeholder="R$ 0,00" /> @@ -86,12 +91,12 @@ function mockFetchPagamentos() { { id: "PAY-001", paciente: { nome: "Sarah Oliveira", convenio: "Unimed" }, - valor: 20000, + valor: 20000, forma_pagamento: "Cartão", data_vencimento: "2025-09-30", status: "pendente", desconto: 0, - observacoes: "Pagamento parcelado em 2x" + observacoes: "Pagamento parcelado em 2x", }, { id: "PAY-002", @@ -100,8 +105,8 @@ function mockFetchPagamentos() { forma_pagamento: "Dinheiro", data_vencimento: "2025-09-15", status: "pago", - desconto: 1000, - observacoes: "" + desconto: 1000, + observacoes: "", }, { id: "PAY-003", @@ -111,18 +116,18 @@ function mockFetchPagamentos() { data_vencimento: "2025-09-20", status: "vencido", desconto: 0, - observacoes: "Não respondeu ao contato" + observacoes: "Não respondeu ao contato", }, - { + { id: "PAY-004", paciente: { nome: "Carlos Almeida", convenio: "Particular" }, valor: 10000, forma_pagamento: "Transferência", data_vencimento: "2025-09-29", status: "pago", - desconto: 500, - observacoes: "Desconto por pagamento adiantado" - } + desconto: 500, + observacoes: "Desconto por pagamento adiantado", + }, ]; } @@ -132,7 +137,11 @@ export default function FinanceiroDashboard() { const [query, setQuery] = useState(""); const [filtroStatus, setFiltroStatus] = useState("Todos"); const [novoPagamento, setNovoPagamento] = useState(false); - const [summary, setSummary] = useState({ totalRecebido: 0, totalAReceber: 0, totalDescontos: 0 }); + const [summary, setSummary] = useState({ + totalRecebido: 0, + totalAReceber: 0, + totalDescontos: 0, + }); useEffect(() => { const data = mockFetchPagamentos(); @@ -141,7 +150,13 @@ export default function FinanceiroDashboard() { function formatCurrency(centavos) { const valorEmReais = centavos / 100; - return "R$ " + valorEmReais.toFixed(2).replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, '.'); + return ( + "R$ " + + valorEmReais + .toFixed(2) + .replace(".", ",") + .replace(/\B(?=(\d{3})+(?!\d))/g, ".") + ); } function getValorLiquido(valor, desconto) { @@ -149,11 +164,12 @@ export default function FinanceiroDashboard() { } const filteredPagamentos = useMemo(() => { - return pagamentos.filter(p => { + return pagamentos.filter((p) => { const q = query.toLowerCase(); const statusOk = filtroStatus === "Todos" || p.status === filtroStatus; - const buscaOk = p.paciente.nome.toLowerCase().includes(q) || - p.id.toLowerCase().includes(q); + const buscaOk = + p.paciente.nome.toLowerCase().includes(q) || + p.id.toLowerCase().includes(q); return statusOk && buscaOk; }); }, [pagamentos, query, filtroStatus]); @@ -163,47 +179,55 @@ export default function FinanceiroDashboard() { let aReceber = 0; let descontos = 0; - filteredPagamentos.forEach(p => { + filteredPagamentos.forEach((p) => { const valorLiquido = getValorLiquido(p.valor, p.desconto); - if (p.status === 'pago') { + if (p.status === "pago") { recebido += valorLiquido; descontos += p.desconto; } else { - aReceber += p.valor; + aReceber += valorLiquido; } }); setSummary({ totalRecebido: recebido, totalAReceber: aReceber, - totalDescontos: descontos + totalDescontos: descontos, }); }, [filteredPagamentos]); function handleDelete(id) { if (window.confirm("Tem certeza que deseja excluir este pagamento?")) { - setPagamentos(prev => prev.filter(p => p.id !== id)); + setPagamentos((prev) => prev.filter((p) => p.id !== id)); setModalPagamento(null); } } function handleSave(pagamento) { - if (!pagamento.paciente.nome || !pagamento.valor || !pagamento.data_vencimento || !pagamento.paciente.convenio) { - alert("Preencha Paciente, Convênio, Valor e Data de Vencimento."); - return; + if ( + !pagamento.paciente.nome || + !pagamento.valor || + !pagamento.data_vencimento || + !pagamento.paciente.convenio + ) { + alert("Preencha Paciente, Convênio, Valor e Data de Vencimento."); + return; } if (novoPagamento) { - const newId = "PAY-" + (pagamentos.length + 1).toString().padStart(3, "0"); - pagamento.id = newId; - setPagamentos(prev => [...prev, pagamento]); + const newId = + "PAY-" + (pagamentos.length + 1).toString().padStart(3, "0"); + pagamento.id = newId; + setPagamentos((prev) => [...prev, pagamento]); } else { - setPagamentos(prev => prev.map(p => p.id === pagamento.id ? pagamento : p)); + setPagamentos((prev) => + prev.map((p) => (p.id === pagamento.id ? pagamento : p)) + ); } setModalPagamento(null); setNovoPagamento(false); } - + const closeModal = () => { setModalPagamento(null); setNovoPagamento(false); @@ -212,51 +236,54 @@ export default function FinanceiroDashboard() { return (

Controle Financeiro

- +
-

Total Recebido (Filtrado)

-

{formatCurrency(summary.totalRecebido)}

+

Total Recebido (Filtrado)

+

{formatCurrency(summary.totalRecebido)}

-

Total a Receber (Filtrado)

-

{formatCurrency(summary.totalAReceber)}

+

Total a Receber (Filtrado)

+

{formatCurrency(summary.totalAReceber)}

-

Descontos Aplicados

-

{formatCurrency(summary.totalDescontos)}

+

Descontos Aplicados

+

{formatCurrency(summary.totalDescontos)}

-
-
- +
+ setQuery(e.target.value)} + placeholder="Buscar paciente" + value={query} + onChange={(e) => setQuery(e.target.value)} style={{ flexGrow: 1 }} /> - setFiltroStatus(e.target.value)} + > -