Răsfoiți Sursa

refactor: ajuste para mostrar erros dos campos + q-scroll-area nos forms em dialogs

Gustavo Mantovani 13 ore în urmă
părinte
comite
e568cdff9a
41 a modificat fișierele cu 593 adăugiri și 208 ștergeri
  1. 3 2
      src/components/defaults/DefaultCurrencyInput.vue
  2. 3 2
      src/components/defaults/DefaultForm.vue
  3. 19 4
      src/components/defaults/DefaultInput.vue
  4. 10 2
      src/components/defaults/DefaultSelect.vue
  5. 1 1
      src/components/defaults/DefaultTable.vue
  6. 12 9
      src/components/layout/LeftMenuLayout.vue
  7. 24 22
      src/components/shared/ChangeImageDialog.vue
  8. 6 23
      src/composables/useSubmitHandler.js
  9. 48 1
      src/css/app.scss
  10. 2 1
      src/pages/apis/ApisPage.vue
  11. 5 3
      src/pages/classes/components/AddEditClassDialog.vue
  12. 5 3
      src/pages/classes/components/JustifyAttendanceDialog.vue
  13. 9 5
      src/pages/dashboard/components/FeriadosDialog.vue
  14. 5 3
      src/pages/dashboard/components/FeriadosEditDialog.vue
  15. 10 6
      src/pages/financial/AccountsPayablePage.vue
  16. 10 6
      src/pages/financial/AccountsReceivablePage.vue
  17. 2 1
      src/pages/financial/AsaasIntegrationPage.vue
  18. 5 3
      src/pages/financial/ChartOfAccountsPage.vue
  19. 5 3
      src/pages/financial/components/AddEditTreasuryAccountDialog.vue
  20. 5 3
      src/pages/financial/components/AddTreasuryLaunchDialog.vue
  21. 5 9
      src/pages/kanban/components/AddEditKanbanDialog.vue
  22. 4 2
      src/pages/login/LoginPage.vue
  23. 5 3
      src/pages/packages/components/AddEditPackageDialog.vue
  24. 27 6
      src/pages/permissions/components/AddEditPermissionGroupDialog.vue
  25. 68 5
      src/pages/students/components/AddEditContractDialog.vue
  26. 43 5
      src/pages/students/components/AddEditStudentDialog.vue
  27. 10 4
      src/pages/students/components/AddStudentMediaDialog.vue
  28. 11 5
      src/pages/students/components/EditStudentDialog.vue
  29. 10 4
      src/pages/students/components/EditStudentMediaDialog.vue
  30. 5 3
      src/pages/students/components/FreezeContractDialog.vue
  31. 40 7
      src/pages/students/components/ResponsibleDialog.vue
  32. 37 0
      src/pages/students/tabs/StudentDataTab.vue
  33. 5 3
      src/pages/support/components/AddEditReplyDialog.vue
  34. 5 6
      src/pages/support/components/AddEditTicketDialog.vue
  35. 5 3
      src/pages/support/components/CloseTicketDialog.vue
  36. 7 4
      src/pages/unit/components/AddEditHistoryDialog.vue
  37. 43 22
      src/pages/unit/components/AddEditPartnerDialog.vue
  38. 7 4
      src/pages/unit/components/AddMediaDialog.vue
  39. 39 2
      src/pages/unit/tabs/UnitDataTab.vue
  40. 13 1
      src/pages/users/UserActionPage.vue
  41. 15 7
      src/pages/users/components/AddEditUserDialog.vue

+ 3 - 2
src/components/defaults/DefaultCurrencyInput.vue

@@ -6,6 +6,7 @@
     :label="label"
     :rules="finalRules"
     :input-class="inputClass"
+    :error-message="errorMessage"
   />
 </template>
 
@@ -20,7 +21,7 @@ const { inputRules } = useInputRules();
 
 const model = defineModel({ type: Number });
 
-const { options, label, rules } = defineProps({
+const { options, label, rules, errorMessage } = defineProps({
   options: {
     type: Object,
     default: () => ({
@@ -41,7 +42,7 @@ const { options, label, rules } = defineProps({
   },
   errorMessage: {
     type: String,
-    default: "",
+    default: undefined,
   },
   inputClass: {
     type: String,

+ 3 - 2
src/components/defaults/DefaultForm.vue

@@ -1,11 +1,12 @@
 <template>
-  <DefaultForm ref="formRef" v-bind="$attrs" @validation-error="onValidationError">
+  <QForm ref="formRef" v-bind="$attrs" @validation-error="onValidationError">
     <slot />
-  </DefaultForm>
+  </QForm>
 </template>
 
 <script setup>
 import { useTemplateRef } from "vue";
+import { QForm } from "quasar";
 import { useFormUpdateTracker } from "src/composables/useFormUpdateTracker";
 
 defineOptions({ inheritAttrs: false });

+ 19 - 4
src/components/defaults/DefaultInput.vue

@@ -10,7 +10,7 @@
         color="secondary"
         :label
         :error="!!error"
-        :error-message="errorMessage"
+        :error-message="normalizedErrorMessage"
         :rules
         :outlined
         :bg-color
@@ -42,8 +42,16 @@ defineOptions({
   inheritAttrs: false,
 });
 
-const { label, nativeInputClass, inputClass, rules, icon, bgColor, outlined } =
-  defineProps({
+const {
+  label,
+  nativeInputClass,
+  inputClass,
+  rules,
+  icon,
+  bgColor,
+  outlined,
+  errorMessage,
+} = defineProps({
     label: {
       type: String,
       default: "",
@@ -72,6 +80,10 @@ const { label, nativeInputClass, inputClass, rules, icon, bgColor, outlined } =
       type: Boolean,
       default: false,
     },
+    errorMessage: {
+      type: String,
+      default: undefined,
+    },
   });
 
 const attrs = useAttrs();
@@ -91,7 +103,10 @@ const inputAttrs = computed(() => {
   return rest;
 });
 
-const errorMessage = computed(() => {
+const normalizedErrorMessage = computed(() => {
+  if (errorMessage != null) {
+    return errorMessage;
+  }
   if (error.value == null) {
     return void 0;
   }

+ 10 - 2
src/components/defaults/DefaultSelect.vue

@@ -8,7 +8,7 @@
         label-color="secondary"
         v-bind="selectAttrs"
         :error="!!error"
-        :error-message="errorMessage"
+        :error-message="normalizedErrorMessage"
         :rules
         :outlined
         hide-bottom-space
@@ -45,6 +45,7 @@ const {
   bgColor,
   outlined,
   dropdownIcon,
+  errorMessage,
 } = defineProps({
   label: {
     type: String,
@@ -74,6 +75,10 @@ const {
     type: String,
     default: "mdi-chevron-down",
   },
+  errorMessage: {
+    type: String,
+    default: undefined,
+  },
 });
 
 const attrs = useAttrs();
@@ -92,7 +97,10 @@ const selectAttrs = computed(() => {
   return rest;
 });
 
-const errorMessage = computed(() => {
+const normalizedErrorMessage = computed(() => {
+  if (errorMessage != null) {
+    return errorMessage;
+  }
   if (error.value == null) {
     return void 0;
   }

+ 1 - 1
src/components/defaults/DefaultTable.vue

@@ -6,7 +6,7 @@
     :pagination="{ rowsPerPage }"
     :pagination-label="getPaginationLabel"
     :rows-per-page-label="$t('common.ui.table.rows_per_page')"
-    :rows-per-page-options="0"
+    :rows-per-page-options="[0]"
     :grid="$q.screen.lt.sm"
     :visible-columns
     :filter

+ 12 - 9
src/components/layout/LeftMenuLayout.vue

@@ -75,13 +75,13 @@
                     color="secondary"
                   />
                 </q-item-section>
-                <q-item-section>{{ $t(item.title) }}</q-item-section>
+                <q-item-section>{{ menuLabel(item.title) }}</q-item-section>
                 <q-tooltip
                   v-if="miniState"
                   anchor="center right"
                   self="center left"
                   :offset="[10, 10]"
-                  >{{ $t(item.title) }}</q-tooltip
+                  >{{ menuLabel(item.title) }}</q-tooltip
                 >
               </q-item>
               <!-- Expansive Menu with children -->
@@ -92,7 +92,7 @@
                     anchor="center right"
                     self="center left"
                     :offset="[10, 10]"
-                    >{{ $t(item.title) }}</q-tooltip
+                    >{{ menuLabel(item.title) }}</q-tooltip
                   >
                   <q-expansion-item
                     v-model="isExpasionItemExpanded[index]"
@@ -110,7 +110,7 @@
                           color="secondary"
                         />
                       </q-item-section>
-                      <q-item-section>{{ $t(item.title) }}</q-item-section>
+                      <q-item-section>{{ menuLabel(item.title) }}</q-item-section>
                     </template>
                     <div
                       v-for="child in item.childrens"
@@ -128,13 +128,13 @@
                         <q-item-section avatar>
                           <q-icon :name="child.icon" style="font-size: 20px" />
                         </q-item-section>
-                        <q-item-section>{{ $t(child.title) }}</q-item-section>
+                        <q-item-section>{{ menuLabel(child.title) }}</q-item-section>
                         <q-tooltip
                           v-if="miniState"
                           anchor="center right"
                           self="center left"
                           :offset="[10, 10]"
-                          >{{ $t(child.title) }}</q-tooltip
+                          >{{ menuLabel(child.title) }}</q-tooltip
                         >
                       </q-item>
                     </div>
@@ -157,13 +157,13 @@
                         color="secondary"
                       />
                     </q-item-section>
-                    <q-item-section>{{ $t(item.title) }}</q-item-section>
+                    <q-item-section>{{ menuLabel(item.title) }}</q-item-section>
                     <q-tooltip
                       v-if="miniState"
                       anchor="center right"
                       self="center left"
                       :offset="[10, 10]"
-                      >{{ $t(item.title) }}</q-tooltip
+                      >{{ menuLabel(item.title) }}</q-tooltip
                     >
                     <q-menu
                       class="menu-drawer"
@@ -188,7 +188,7 @@
                               style="font-size: 20px"
                             />
                           </q-item-section>
-                          <q-item-section>{{ $t(child.title) }}</q-item-section>
+                          <q-item-section>{{ menuLabel(child.title) }}</q-item-section>
                         </q-item>
                       </q-list>
                     </q-menu>
@@ -263,6 +263,7 @@ import { useRouter, useRoute } from "vue-router";
 import { navigationStore } from "src/stores/navigation";
 import { useQuasar, Cookies } from "quasar";
 import { version } from "src/../package.json";
+import { useI18n } from "vue-i18n";
 
 import Logo from "src/assets/images/logo.svg";
 import MiniLogo from "src/assets/images/mini-logo.svg";
@@ -275,6 +276,8 @@ const router = useRouter();
 const route = useRoute();
 const navigation_store = navigationStore();
 const $q = useQuasar();
+const { t, te } = useI18n();
+const menuLabel = (label) => (te(label) ? t(label) : label);
 
 const miniStateCookies = Cookies.get("miniState");
 const miniState = ref(miniStateCookies === "true" ? true : false);

+ 24 - 22
src/components/shared/ChangeImageDialog.vue

@@ -1,30 +1,32 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin overflow-hidden" style="min-width: 400px">
+    <q-card class="q-dialog-plugin dialog-form-card" style="min-width: 400px">
       <DefaultDialogHeader title="Trocar Imagem" @close="onDialogCancel" />
 
-      <q-card-section class="q-pt-none q-pb-sm">
-        <div class="text-caption text-grey-6 q-mb-xs">Personalizar</div>
-        <q-file
-          v-model="selectedFile"
-          accept="image/*"
-          outlined
-          dense
-          placeholder="Buscar no Desktop"
-          @update:model-value="onFileSelected"
-        >
-          <template #append>
-            <q-icon name="search" />
-          </template>
-        </q-file>
-      </q-card-section>
+      <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+        <q-card-section class="q-pt-none q-pb-sm">
+          <div class="text-caption text-grey-6 q-mb-xs">Personalizar</div>
+          <q-file
+            v-model="selectedFile"
+            accept="image/*"
+            outlined
+            dense
+            placeholder="Buscar no Desktop"
+            @update:model-value="onFileSelected"
+          >
+            <template #append>
+              <q-icon name="search" />
+            </template>
+          </q-file>
+        </q-card-section>
 
-      <q-card-section class="q-pt-none">
-        <div class="text-caption text-grey-6 q-mb-xs">Pré - Visualização</div>
-        <div class="preview-area flex flex-center">
-          <img v-if="previewUrl" :src="previewUrl" class="preview-image" />
-        </div>
-      </q-card-section>
+        <q-card-section class="q-pt-none">
+          <div class="text-caption text-grey-6 q-mb-xs">Pré - Visualização</div>
+          <div class="preview-area flex flex-center">
+            <img v-if="previewUrl" :src="previewUrl" class="preview-image" />
+          </div>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-card-actions align="right">
         <q-btn

+ 6 - 23
src/composables/useSubmitHandler.js

@@ -1,7 +1,9 @@
 import { ref, nextTick } from "vue";
+import { useScroll } from "src/composables/useScroll";
 
 export function useSubmitHandler(options = {}) {
   const { onSuccess, onError, formRef, scrollFn, containerRef } = options;
+  const { scrollToFirstError } = useScroll();
 
   const loading = ref(false);
   const validationErrors = ref({});
@@ -12,27 +14,8 @@ export function useSubmitHandler(options = {}) {
     return Array.isArray(refs) ? refs : [refs];
   };
 
-  const scrollToFirstError = async () => {
-    if (!formRef?.value || !scrollFn) return;
-    await nextTick();
-
-    const refsToSearch = getFormRefs();
-
-    for (const ref of refsToSearch) {
-      if (!ref) continue;
-      const components = ref.getValidationComponents();
-      if (!components) continue;
-
-      const firstErrorComponent = components.find((c) => c.hasError);
-
-      if (firstErrorComponent) {
-        const container = containerRef?.value || containerRef;
-        firstErrorComponent.focus();
-        scrollFn(firstErrorComponent, container);
-        return;
-      }
-    }
-  };
+  const showFirstError = () =>
+    scrollToFirstError(formRef, containerRef, { scrollFn });
 
   const execute = async (apiCallThunk) => {
     loading.value = true;
@@ -54,7 +37,7 @@ export function useSubmitHandler(options = {}) {
 
     if (!allValid) {
       loading.value = false;
-      await scrollToFirstError();
+      await showFirstError();
       throw new Error("Frontend validation failed.");
     }
 
@@ -79,7 +62,7 @@ export function useSubmitHandler(options = {}) {
         const message = errors[key][0];
         validationErrors.value[key] = message;
       }
-      await scrollToFirstError();
+      await showFirstError();
     }
 
     await nextTick();

+ 48 - 1
src/css/app.scss

@@ -78,6 +78,54 @@ body.body--light {
   padding: 10px 16px;
 }
 
+.dialog-form-card {
+  display: flex;
+  flex-direction: column;
+  max-height: calc(100vh - 48px);
+  overflow: hidden;
+}
+
+.dialog-form-card > .q-form {
+  display: flex;
+  flex: 1 1 auto;
+  flex-direction: column;
+  min-height: 0;
+  overflow: hidden;
+}
+
+.dialog-form-card > .q-bar,
+.dialog-form-card > .q-separator {
+  flex: 0 0 auto;
+}
+
+.dialog-form-card > .dialog-form-scroll,
+.dialog-form-card > .q-form > .dialog-form-scroll {
+  flex: 1 1 auto;
+  height: min(
+    var(--dialog-form-scroll-height, 65vh),
+    calc(100vh - 160px)
+  );
+  min-height: 0;
+  overflow: hidden;
+}
+
+.dialog-form-scroll--xs {
+  --dialog-form-scroll-height: 160px;
+}
+
+.dialog-form-scroll--sm {
+  --dialog-form-scroll-height: 300px;
+}
+
+.dialog-form-scroll--md {
+  --dialog-form-scroll-height: 440px;
+}
+
+.dialog-form-card > .q-card__actions,
+.dialog-form-card > .q-form > .q-card__actions {
+  flex: 0 0 auto;
+}
+
 input[type="number"]::-webkit-inner-spin-button,
 input[type="number"]::-webkit-outer-spin-button {
   -webkit-appearance: none;
@@ -131,4 +179,3 @@ input[type="number"]::-webkit-outer-spin-button {
 .q-field--standout.q-field--highlighted .q-field__native {
   color: $secondary;
 }
-

+ 2 - 1
src/pages/apis/ApisPage.vue

@@ -25,7 +25,8 @@
         <DefaultForm ref="formRef">
           <DefaultPasswordInput
             v-model="apiKey"
-            v-model:error="validationErrors.asaas_api_key"
+            :error="!!validationErrors.asaas_api_key"
+            :error-message="validationErrors.asaas_api_key"
             :label="
               account.asaas_configured
                 ? 'Nova chave da API (deixe em branco para manter a atual)'

+ 5 - 3
src/pages/classes/components/AddEditClassDialog.vue

@@ -1,14 +1,15 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 560px">
-      <q-card style="display: flex; flex-direction: column; overflow: hidden">
+      <q-card class="dialog-form-card">
         <DefaultDialogHeader
           :title="classData ? 'Editar Aula' : 'Nova Aula'"
           @close="onDialogCancel"
         />
 
         <DefaultForm ref="formRef" @submit="onSubmit">
-          <q-card-section class="q-pt-sm">
+          <q-scroll-area class="dialog-form-scroll dialog-form-scroll--md">
+            <q-card-section class="q-pt-sm">
             <div class="row q-col-gutter-sm">
               <!-- Nome da Atividade -->
               <DefaultInput
@@ -108,7 +109,8 @@
                 :rules="[(v) => !!v || 'Campo obrigatório']"
               />
             </div>
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-px-md q-pb-md">
             <q-btn outline color="primary" label="Cancelar" no-caps @click="onDialogCancel" />

+ 5 - 3
src/pages/classes/components/JustifyAttendanceDialog.vue

@@ -1,10 +1,11 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 520px">
-      <q-card>
+      <q-card class="dialog-form-card">
         <DefaultDialogHeader title="Justifica Registrar Presença" @close="onDialogCancel" />
 
-        <q-card-section class="q-pt-sm">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="q-pt-sm">
           <q-input
             v-model="text"
             type="textarea"
@@ -19,7 +20,8 @@
               <q-icon name="mdi-pencil-outline" color="secondary" />
             </template>
           </q-input>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-px-md q-pb-md">
           <q-btn outline color="primary" label="Cancelar" no-caps @click="onDialogCancel" />

+ 9 - 5
src/pages/dashboard/components/FeriadosDialog.vue

@@ -1,10 +1,11 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin feriados-dialog">
+    <q-card class="q-dialog-plugin dialog-form-card feriados-dialog">
       <template v-if="view === 'calendar'">
         <DefaultDialogHeader title="Feriados" @close="onClose" />
 
-        <q-card-section class="row q-col-gutter-md q-pt-none">
+        <q-scroll-area class="dialog-form-scroll">
+          <q-card-section class="row q-col-gutter-md q-pt-none">
           <div class="col-12 col-sm-7">
             <div class="row items-center justify-between q-mb-md">
               <q-btn
@@ -131,7 +132,8 @@
               Clique em um dia no calendário para adicionar um registro.
             </div>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-separator />
 
@@ -152,7 +154,8 @@
           @close="cancelNewRecord"
         />
 
-        <q-card-section class="column q-gutter-md q-pt-sm">
+        <q-scroll-area class="dialog-form-scroll">
+          <q-card-section class="column q-gutter-md q-pt-sm">
           <DefaultInput
             v-model="newDescription"
             label="Nome do Evento"
@@ -170,7 +173,8 @@
             map-options
             outlined
           />
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-separator />
 

+ 5 - 3
src/pages/dashboard/components/FeriadosEditDialog.vue

@@ -1,9 +1,10 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin feriados-edit-dialog">
+    <q-card class="q-dialog-plugin dialog-form-card feriados-edit-dialog">
       <DefaultDialogHeader :title="dialogTitle" @close="onDialogCancel" />
 
-      <q-card-section class="column q-gutter-md q-pt-sm">
+      <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+        <q-card-section class="column q-gutter-md q-pt-sm">
         <q-banner
           v-if="isReadOnly"
           dense
@@ -33,7 +34,8 @@
           outlined
           :disable="isReadOnly || !canEdit"
         />
-      </q-card-section>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-separator />
 

+ 10 - 6
src/pages/financial/AccountsPayablePage.vue

@@ -89,10 +89,11 @@
 
     <!-- Diálogo de baixa manual -->
     <q-dialog v-model="settleDialog">
-      <q-card style="min-width: 360px; max-width: 460px">
+      <q-card class="dialog-form-card" style="min-width: 360px; max-width: 460px">
         <DefaultDialogHeader title="Dar baixa" @close="settleDialog = false" />
 
-        <q-card-section class="q-gutter-sm">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="q-gutter-sm">
           <div class="text-body2 text-grey-8">{{ selected?.history }}</div>
 
           <DefaultInputDatePicker
@@ -122,7 +123,8 @@
               Pago: {{ formatCurrency(settleNetValue) }}
             </span>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn flat label="Cancelar" color="grey-7" @click="settleDialog = false" />
@@ -138,11 +140,12 @@
 
     <!-- Diálogo de nova conta manual -->
     <q-dialog v-model="createDialog">
-      <q-card style="min-width: 360px; max-width: 460px">
+      <q-card class="dialog-form-card" style="min-width: 360px; max-width: 460px">
         <DefaultDialogHeader title="Nova conta a pagar" @close="createDialog = false" />
 
         <DefaultForm ref="createFormRef">
-          <q-card-section class="q-gutter-sm">
+          <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+            <q-card-section class="q-gutter-sm">
             <DefaultInput
               v-model="createForm.history"
               label="Descrição"
@@ -170,7 +173,8 @@
               type="textarea"
               outlined
             />
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-pa-md">
             <q-btn flat label="Cancelar" color="grey-7" @click="createDialog = false" />

+ 10 - 6
src/pages/financial/AccountsReceivablePage.vue

@@ -97,14 +97,15 @@
 
     <!-- Diálogo de novo recebível avulso -->
     <q-dialog v-model="createDialog">
-      <q-card style="min-width: 380px; max-width: 480px">
+      <q-card class="dialog-form-card" style="min-width: 380px; max-width: 480px">
         <DefaultDialogHeader
           title="Nova conta a receber"
           @close="createDialog = false"
         />
 
         <DefaultForm ref="createFormRef">
-          <q-card-section class="q-gutter-sm">
+          <q-scroll-area class="dialog-form-scroll dialog-form-scroll--md">
+            <q-card-section class="q-gutter-sm">
             <DefaultInput
               v-model="createForm.history"
               label="Descrição"
@@ -155,7 +156,8 @@
               autogrow
               outlined
             />
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-pa-md">
             <q-btn flat label="Cancelar" color="grey-7" @click="createDialog = false" />
@@ -172,10 +174,11 @@
 
     <!-- Diálogo de baixa manual -->
     <q-dialog v-model="settleDialog">
-      <q-card style="min-width: 360px; max-width: 460px">
+      <q-card class="dialog-form-card" style="min-width: 360px; max-width: 460px">
         <DefaultDialogHeader title="Dar baixa" @close="settleDialog = false" />
 
-        <q-card-section class="q-gutter-sm">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="q-gutter-sm">
           <div class="text-body2 text-grey-8">
             {{ selected?.student_name }} — {{ selected?.history }}
             ({{ selected?.order }})
@@ -208,7 +211,8 @@
               Recebido: {{ formatCurrency(settleNetValue) }}
             </span>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn flat label="Cancelar" color="grey-7" @click="settleDialog = false" />

+ 2 - 1
src/pages/financial/AsaasIntegrationPage.vue

@@ -24,7 +24,8 @@
         <DefaultForm ref="formRef" @submit.prevent="onSave">
           <DefaultPasswordInput
             v-model="apiKey"
-            v-model:error="validationErrors.asaas_api_key"
+            :error="!!validationErrors.asaas_api_key"
+            :error-message="validationErrors.asaas_api_key"
             :label="account.asaas_configured ? 'Nova chave (deixe em branco para manter)' : 'Chave da API do Asaas'"
             :placeholder="account.asaas_api_key_masked || ''"
             autocomplete="off"

+ 5 - 3
src/pages/financial/ChartOfAccountsPage.vue

@@ -31,14 +31,15 @@
     </div>
 
     <q-dialog v-model="dialog">
-      <q-card style="min-width: 360px; max-width: 460px">
+      <q-card class="dialog-form-card" style="min-width: 360px; max-width: 460px">
         <DefaultDialogHeader
           :title="editing ? 'Editar conta' : 'Nova conta'"
           @close="dialog = false"
         />
 
         <DefaultForm ref="formRef">
-          <q-card-section class="q-gutter-sm">
+          <q-scroll-area class="dialog-form-scroll dialog-form-scroll--md">
+            <q-card-section class="q-gutter-sm">
             <q-option-group
               v-model="accountKind"
               :options="kindOptions"
@@ -92,7 +93,8 @@
               Tipo herdado do pai:
               <b>{{ chartTypeLabel(selectedParent.chart_type) }}</b>
             </div>
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-pa-md">
             <q-btn flat label="Cancelar" color="grey-7" @click="dialog = false" />

+ 5 - 3
src/pages/financial/components/AddEditTreasuryAccountDialog.vue

@@ -1,10 +1,11 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin overflow-hidden treasury-dialog-card">
+    <q-card class="q-dialog-plugin dialog-form-card treasury-dialog-card">
       <DefaultDialogHeader :title="title" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="row q-col-gutter-md q-px-lg q-pt-md q-pb-sm">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="row q-col-gutter-md q-px-lg q-pt-md q-pb-sm">
           <DefaultInput
             v-model="form.name"
             label="Nome (ex.: Banco do Brasil, Caixa Loja)"
@@ -40,7 +41,8 @@
             emit-value
             map-options
           />
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-px-lg q-pt-md q-pb-lg q-gutter-sm">
           <q-btn flat label="Cancelar" color="grey-7" @click="onDialogCancel" />

+ 5 - 3
src/pages/financial/components/AddTreasuryLaunchDialog.vue

@@ -1,10 +1,11 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin overflow-hidden treasury-dialog-card">
+    <q-card class="q-dialog-plugin dialog-form-card treasury-dialog-card">
       <DefaultDialogHeader :title="launch ? 'Editar movimentação' : 'Nova movimentação'" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="row q-col-gutter-md q-px-lg q-pt-md q-pb-sm">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="row q-col-gutter-md q-px-lg q-pt-md q-pb-sm">
           <DefaultSelect
             v-model="form.transaction_type"
             label="Tipo"
@@ -33,7 +34,8 @@
             label="Data"
             class="col-12 col-sm-6"
           />
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-px-lg q-pt-md q-pb-lg q-gutter-sm">
           <q-btn flat label="Cancelar" color="grey-7" @click="onDialogCancel" />

+ 5 - 9
src/pages/kanban/components/AddEditKanbanDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 1100px">
-      <q-card style="height: 560px; display: flex; flex-direction: column; overflow: hidden">
+      <q-card class="dialog-form-card" style="height: 560px">
         <DefaultDialogHeader
           :title="card ? 'Editar Atividade' : 'Adicionar Atividade'"
           @close="onDialogCancel"
@@ -12,7 +12,8 @@
           style="flex: 1; display: flex; flex-direction: column; overflow: hidden"
           @submit="onOKClick"
         >
-          <q-card-section class="q-pt-sm" style="flex: 1; overflow-y: auto; min-height: 0">
+          <q-scroll-area class="dialog-form-scroll">
+            <q-card-section class="q-pt-sm">
             <CustomTabComponent
               v-if="card?.id"
               v-model:active-tab="currentTab"
@@ -114,9 +115,6 @@
               </div>
               <div
                 style="
-                  height: 100%;
-                  max-height: 380px;
-                  overflow-y: auto;
                   display: flex;
                   flex-direction: column;
                   gap: 8px;
@@ -159,9 +157,6 @@
               </div>
               <div
                 style="
-                  height: 100%;
-                  max-height: 380px;
-                  overflow-y: auto;
                   display: flex;
                   flex-direction: column;
                   gap: 8px;
@@ -184,7 +179,8 @@
                 </div>
               </div>
             </div>
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-px-md q-pb-md" style="flex-shrink: 0">
             <q-btn outline color="primary" label="Cancelar" @click="onDialogCancel" />

+ 4 - 2
src/pages/login/LoginPage.vue

@@ -41,7 +41,8 @@
       >
         <DefaultInput
           v-model="form.email"
-          v-model:error="validationErrors.email"
+          :error="!!validationErrors.email"
+          :error-message="validationErrors.email"
           type="email"
           lazy-rules
           style="color: red"
@@ -52,7 +53,8 @@
 
         <DefaultPasswordInput
           v-model="form.password"
-          v-model:error="validationErrors.password"
+          :error="!!validationErrors.password"
+          :error-message="validationErrors.password"
           :rules="[inputRules.required, inputRules.min(6)]"
           :label="$t('common.terms.password')"
         />

+ 5 - 3
src/pages/packages/components/AddEditPackageDialog.vue

@@ -1,14 +1,15 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 700px">
-      <q-card class="overflow-hidden" style="width: 100%">
+      <q-card class="dialog-form-card" style="width: 100%">
         <DefaultDialogHeader
           :title="props.package ? 'Editar Pacote' : 'Novo Pacote'"
           @close="onDialogCancel"
         />
 
         <DefaultForm ref="formRef" @submit="onOKClick">
-          <q-card-section class="q-pt-sm">
+          <q-scroll-area class="dialog-form-scroll">
+            <q-card-section class="q-pt-sm">
             <div class="row q-col-gutter-sm">
               <DefaultInput
                 v-model="form.name"
@@ -163,7 +164,8 @@
                 </div>
               </div>
             </div>
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-px-md q-pb-md">
             <q-btn

+ 27 - 6
src/pages/permissions/components/AddEditPermissionGroupDialog.vue

@@ -1,12 +1,30 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card style="min-width: 440px; max-width: 90vw">
+    <q-card class="dialog-form-card" style="min-width: 440px; max-width: 90vw">
       <DefaultDialogHeader :title="isEdit ? 'Editar Grupo de Permissões' : 'Novo Grupo de Permissões'" @close="onDialogCancel" />
       <DefaultForm ref="formRef" @submit="onSave">
-        <q-card-section class="column q-gutter-md">
-          <DefaultInput v-model="form.label" label="Nome" autofocus :rules="[inputRules.required]" @update:model-value="onLabelChange" />
-          <DefaultInput v-model="form.slug" label="Código" hint="Identificador único. Ex.: FINANCEIRO" :rules="[inputRules.required, slugRule]" @update:model-value="onSlugTouched" />
-        </q-card-section>
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="column q-gutter-md">
+          <DefaultInput
+            v-model="form.label"
+            label="Nome"
+            autofocus
+            :rules="[inputRules.required]"
+            :error="!!validationErrors.label"
+            :error-message="validationErrors.label"
+            @update:model-value="onLabelChange"
+          />
+          <DefaultInput
+            v-model="form.slug"
+            label="Código"
+            hint="Identificador único. Ex.: FINANCEIRO"
+            :rules="[inputRules.required, slugRule]"
+            :error="!!validationErrors.slug"
+            :error-message="validationErrors.slug"
+            @update:model-value="onSlugTouched"
+          />
+          </q-card-section>
+        </q-scroll-area>
         <q-card-actions align="right">
           <q-btn label="Cancelar" color="negative" outline @click="onDialogCancel" />
           <q-btn label="Salvar" color="secondary" type="submit" :loading="loading" />
@@ -37,7 +55,10 @@ const toSlug = (value) => (value ?? "").normalize("NFD").replace(/\p{Diacritic}/
 const slugRule = (value) => /^[A-Z0-9_]+$/.test(value ?? "") || "Use apenas letras maiúsculas, números e underline.";
 const onLabelChange = (value) => { if (!slugTouched.value) form.value.slug = toSlug(value); };
 const onSlugTouched = (value) => { slugTouched.value = true; form.value.slug = toSlug(value); };
-const { loading, execute } = useSubmitHandler({ formRef, onSuccess: () => onDialogOK() });
+const { loading, validationErrors, execute } = useSubmitHandler({
+  formRef,
+  onSuccess: () => onDialogOK(),
+});
 const onSave = () => execute(() => isEdit.value
   ? updateUserType(group.id, form.value)
   : createUserType(form.value));

+ 68 - 5
src/pages/students/components/AddEditContractDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden column no-wrap"
+      class="q-dialog-plugin dialog-form-card"
       :style="dialogCardStyle"
     >
       <DefaultDialogHeader
@@ -24,7 +24,8 @@
         <q-separator />
       </template>
 
-      <q-card-section class="q-pt-sm col" style="overflow-y: auto">
+      <q-scroll-area class="dialog-form-scroll">
+        <q-card-section class="q-pt-sm">
         <div v-show="activeTab === 'dados'">
         <div class="text-subtitle1 q-mb-md">Dados do Aluno</div>
 
@@ -33,6 +34,8 @@
             <div class="row items-start no-wrap q-col-gutter-sm">
               <DefaultSelect
                 v-model="selectedStudent"
+                :error="!!validationErrors.student_id"
+                :error-message="validationErrors.student_id"
                 label="Aluno"
                 class="col"
                 :options="filteredStudents"
@@ -63,6 +66,8 @@
           <div v-if="!props.selectStudent" class="col-12">
             <DefaultInput
               :model-value="currentStudent?.name"
+              :error="!!validationErrors.student_id"
+              :error-message="validationErrors.student_id"
               label="Aluno"
               disable
             />
@@ -71,6 +76,8 @@
           <div v-if="props.selectStudent" class="col-12 col-md-4">
             <DefaultInput
               :model-value="currentStudent?.id"
+              :error="!!validationErrors.student_id"
+              :error-message="validationErrors.student_id"
               label="ID do Aluno"
               disable
             />
@@ -79,6 +86,8 @@
           <div :class="props.selectStudent ? 'col-12 col-md-4' : 'col-6'">
             <DefaultInput
               :model-value="currentStudent?.document_number"
+              :error="!!validationErrors.document_number"
+              :error-message="validationErrors.document_number"
               label="CPF / CNH"
               disable
             />
@@ -87,6 +96,8 @@
           <div :class="props.selectStudent ? 'col-12 col-md-4' : 'col-6'">
             <DefaultInput
               :model-value="formattedBirthDate"
+              :error="!!validationErrors.birth_date"
+              :error-message="validationErrors.birth_date"
               label="Data de Nascimento"
               disable
             />
@@ -97,12 +108,19 @@
 
         <div class="row q-col-gutter-sm">
           <div class="col-4">
-            <DefaultInput v-model="form.protocol" label="Protocolo" />
+            <DefaultInput
+              v-model="form.protocol"
+              label="Protocolo"
+              :error="!!validationErrors.protocol"
+              :error-message="validationErrors.protocol"
+            />
           </div>
 
           <div class="col-4">
             <DefaultInputDatePicker
               v-model="form.signature_date"
+              :error="!!validationErrors.signature_date"
+              :error-message="validationErrors.signature_date"
               label="Data Assinatura"
             />
           </div>
@@ -110,6 +128,8 @@
           <div class="col-4">
             <DefaultInputDatePicker
               v-model="form.end_date"
+              :error="!!validationErrors.end_date"
+              :error-message="validationErrors.end_date"
               label="Data Encerramento"
             />
           </div>
@@ -117,6 +137,8 @@
           <div class="col-5">
             <DefaultSelect
               v-model="form.package_id"
+              :error="!!validationErrors.class_package_unit_id"
+              :error-message="validationErrors.class_package_unit_id"
               label="Pacote de Aulas"
               :options="packages"
               option-value="id"
@@ -129,6 +151,8 @@
           <div class="col-7">
             <DefaultInput
               v-model="form.class_quantity"
+              :error="!!validationErrors.class_quantity"
+              :error-message="validationErrors.class_quantity"
               label="Qtd. Aulas"
               type="number"
               disable
@@ -138,6 +162,8 @@
           <div class="col-4">
             <DefaultSelect
               v-model="form.weekday"
+              :error="!!validationErrors.weekday"
+              :error-message="validationErrors.weekday"
               label="Dia da Semana"
               :options="weekdays"
               option-value="value"
@@ -150,6 +176,8 @@
           <div class="col-4">
             <DefaultInput
               v-model="form.start_time"
+              :error="!!validationErrors.start_time"
+              :error-message="validationErrors.start_time"
               label="Hora de Início"
               mask="##:##"
             >
@@ -162,6 +190,8 @@
           <div class="col-4">
             <DefaultInput
               v-model="form.end_time"
+              :error="!!validationErrors.end_time"
+              :error-message="validationErrors.end_time"
               label="Hora de Término"
               mask="##:##"
             >
@@ -174,6 +204,8 @@
           <div class="col-4">
             <DefaultSelect
               v-model="form.second_weekday"
+              :error="!!validationErrors.second_weekday"
+              :error-message="validationErrors.second_weekday"
               label="2° Dia da Semana"
               :options="weekdays"
               option-value="value"
@@ -186,6 +218,8 @@
           <div class="col-4">
             <DefaultInput
               v-model="form.second_start_time"
+              :error="!!validationErrors.second_start_time"
+              :error-message="validationErrors.second_start_time"
               label="Hora de Início"
               mask="##:##"
             >
@@ -198,6 +232,8 @@
           <div class="col-4">
             <DefaultInput
               v-model="form.second_end_time"
+              :error="!!validationErrors.second_end_time"
+              :error-message="validationErrors.second_end_time"
               label="Hora de Término"
               mask="##:##"
             >
@@ -214,6 +250,8 @@
           <div class="col-4">
             <DefaultInput
               v-model="form.due_day"
+              :error="!!validationErrors.due_day"
+              :error-message="validationErrors.due_day"
               label="Dia de Vencimento"
               type="number"
             />
@@ -225,6 +263,8 @@
           <div class="col-3">
             <DefaultCurrencyInput
               v-model="form.enrollment_fee"
+              :error="!!validationErrors.tax_register"
+              :error-message="validationErrors.tax_register"
               label="Valor da Matrícula"
               disable
             />
@@ -233,6 +273,8 @@
           <div class="col-3">
             <DefaultSelect
               v-model="form.installments"
+              :error="!!validationErrors.installments"
+              :error-message="validationErrors.installments"
               label="Qtde Parcelas"
               :options="enrollmentInstallmentOptions"
               option-value="value"
@@ -245,6 +287,8 @@
           <div class="col-3">
             <DefaultCurrencyInput
               :model-value="enrollmentInstallmentValue"
+              :error="!!validationErrors.installments"
+              :error-message="validationErrors.installments"
               label="Valor da Parcela"
               disable
             />
@@ -253,6 +297,8 @@
           <div class="col-3">
             <DefaultInputDatePicker
               v-model="form.enrollment_due_date"
+              :error="!!validationErrors.enrollment_due_date"
+              :error-message="validationErrors.enrollment_due_date"
               label="Data Vencimento"
             />
           </div>
@@ -263,6 +309,8 @@
           <div class="col-3">
             <DefaultCurrencyInput
               v-model="form.package_value"
+              :error="!!validationErrors.package_value"
+              :error-message="validationErrors.package_value"
               label="Valor do Pacote"
               disable
             />
@@ -271,6 +319,8 @@
           <div class="col-3">
             <DefaultSelect
               v-model="form.package_installments"
+              :error="!!validationErrors.package_installments"
+              :error-message="validationErrors.package_installments"
               label="Qtde Parcelas"
               :options="packageInstallmentOptions"
               option-value="value"
@@ -283,6 +333,8 @@
           <div class="col-3">
             <DefaultCurrencyInput
               :model-value="packageInstallmentValue"
+              :error="!!validationErrors.package_installments"
+              :error-message="validationErrors.package_installments"
               label="Valor da Parcela"
               disable
             />
@@ -291,6 +343,8 @@
           <div class="col-3">
             <DefaultInputDatePicker
               v-model="form.package_due_date"
+              :error="!!validationErrors.package_due_date"
+              :error-message="validationErrors.package_due_date"
               label="Data 1ª Parcela"
             />
           </div>
@@ -300,6 +354,8 @@
           <div class="col-6">
             <DefaultInput
               v-model="form.early_payment_discount"
+              :error="!!validationErrors.early_payment_discount"
+              :error-message="validationErrors.early_payment_discount"
               label="Desconto até o vencimento (%)"
               type="number"
             />
@@ -308,6 +364,8 @@
           <div class="col-6">
             <DefaultSelect
               v-model="form.payment_method"
+              :error="!!validationErrors.payment_method"
+              :error-message="validationErrors.payment_method"
               label="Forma de Pagamento"
               :options="paymentMethods"
               option-value="value"
@@ -320,6 +378,8 @@
           <div class="col-6">
             <DefaultInput
               v-model="form.interest_rate"
+              :error="!!validationErrors.interest_rate"
+              :error-message="validationErrors.interest_rate"
               label="Juros (%) a.m"
               type="number"
             />
@@ -328,6 +388,8 @@
           <div class="col-6">
             <DefaultInput
               v-model="form.late_fee"
+              :error="!!validationErrors.fine_cancelled"
+              :error-message="validationErrors.fine_cancelled"
               label="Multa (%)"
               type="number"
             />
@@ -367,7 +429,8 @@
             </template>
           </DefaultTable>
         </div>
-      </q-card-section>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-separator />
 
@@ -783,7 +846,7 @@ const formattedBirthDate = computed(() =>
     : "",
 );
 
-const { loading: saving, execute } = useSubmitHandler({
+const { loading: saving, validationErrors, execute } = useSubmitHandler({
   onSuccess: () => onDialogOK(true),
 });
 

+ 43 - 5
src/pages/students/components/AddEditStudentDialog.vue

@@ -1,13 +1,14 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 100%; max-width: 1100px"
     >
       <DefaultDialogHeader title="Cadastrar Aluno" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll">
+          <q-card-section class="q-pt-none">
           <div class="flex justify-center q-mb-md">
             <AvatarImageComponent
               ref="avatarRef"
@@ -18,6 +19,8 @@
           <div class="row q-col-gutter-sm">
             <DefaultInput
               v-model="form.name"
+              :error="!!validationErrors.name"
+              :error-message="validationErrors.name"
               label="Nome do Aluno"
               class="col-6"
               :rules="[inputRules.required]"
@@ -25,12 +28,16 @@
 
             <DefaultInputDatePicker
               v-model="form.birthdate"
+              :error="!!validationErrors.birth_date"
+              :error-message="validationErrors.birth_date"
               label="Data de Nascimento"
               class="col-6"
             />
 
             <DefaultInput
               v-model="form.cpf"
+              :error="!!validationErrors.document_number"
+              :error-message="validationErrors.document_number"
               label="CPF"
               class="col-6"
               :mask="masks.Brasil.cpf"
@@ -39,6 +46,8 @@
 
             <DefaultSelect
               v-model="form.gender"
+              :error="!!validationErrors.gender"
+              :error-message="validationErrors.gender"
               label="Gênero"
               class="col-6"
               emit-value
@@ -48,6 +57,8 @@
 
             <DefaultInput
               v-model="form.email"
+              :error="!!validationErrors.email"
+              :error-message="validationErrors.email"
               label="E-mail"
               class="col-6"
               type="email"
@@ -56,6 +67,8 @@
 
             <DefaultInput
               v-model="form.phone"
+              :error="!!validationErrors.phone"
+              :error-message="validationErrors.phone"
               label="Celular com DDD"
               class="col-6"
               :mask="masks.Brasil.celular"
@@ -63,6 +76,8 @@
 
             <DefaultCepInput
               v-model="form.cep"
+              :error="!!validationErrors.postal_code"
+              :error-message="validationErrors.postal_code"
               class="col-4"
               :rules="[inputRules.cep]"
               @rua="(v) => (form.address = v)"
@@ -73,18 +88,24 @@
 
             <DefaultInput
               v-model="form.address"
+              :error="!!validationErrors.street"
+              :error-message="validationErrors.street"
               label="Endereço"
               class="col-5"
             />
 
             <DefaultInput
               v-model="form.address_number"
+              :error="!!validationErrors.address_number"
+              :error-message="validationErrors.address_number"
               label="Número"
               class="col-3"
             />
 
             <DefaultInput
               v-model="form.neighborhood"
+              :error="!!validationErrors.neighborhood"
+              :error-message="validationErrors.neighborhood"
               label="Bairro"
               class="col-4"
             />
@@ -92,6 +113,8 @@
             <CitySelect
               ref="citySelectRef"
               v-model="selectedCity"
+              :error="!!validationErrors.city_id"
+              :error-message="validationErrors.city_id"
               label="Cidade"
               class="col-4"
               :state="selectedState"
@@ -100,20 +123,32 @@
             <StateSelect
               ref="stateSelectRef"
               v-model="selectedState"
+              :error="!!validationErrors.state_id"
+              :error-message="validationErrors.state_id"
               label="Estado"
               class="col-4"
             />
 
             <DefaultInput
               v-model="form.complement"
+              :error="!!validationErrors.complement"
+              :error-message="validationErrors.complement"
               label="Complemento"
               class="col-6"
             />
 
-            <DefaultInput v-model="form.payer" label="Pagador" class="col-6" />
+            <DefaultInput
+              v-model="form.payer"
+              label="Pagador"
+              class="col-6"
+              :error="!!validationErrors.payer_name"
+              :error-message="validationErrors.payer_name"
+            />
 
             <DefaultSelect
               v-model="form.how_found"
+              :error="!!validationErrors.how_did_you_know_us"
+              :error-message="validationErrors.how_did_you_know_us"
               label="Como nos conheceu?"
               class="col-12"
               emit-value
@@ -123,6 +158,8 @@
 
             <DefaultInput
               v-model="form.notes"
+              :error="!!validationErrors.notes"
+              :error-message="validationErrors.notes"
               label="Observações"
               class="col-12"
               type="textarea"
@@ -130,7 +167,8 @@
               autogrow
             />
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right">
           <q-btn
@@ -225,7 +263,7 @@ const howFoundOptions = ref([
   { label: "Outro", value: "other" },
 ]);
 
-const { loading, execute } = useSubmitHandler({
+const { loading, validationErrors, execute } = useSubmitHandler({
   formRef,
   onSuccess: (student) => {
     onDialogOK(student);

+ 10 - 4
src/pages/students/components/AddStudentMediaDialog.vue

@@ -1,19 +1,24 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin" style="width: 480px; max-width: 95vw">
+    <q-card class="q-dialog-plugin dialog-form-card" style="width: 480px; max-width: 95vw">
       <DefaultDialogHeader title="Adicionar Mídia" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onSubmit">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="q-pt-none">
           <div class="column q-gutter-sm">
             <DefaultInput
               v-model="form.name"
+              :error="!!validationErrors.name"
+              :error-message="validationErrors.name"
               label="Nome do documento"
               :rules="[inputRules.required]"
             />
 
             <q-file
               v-model="selectedFile"
+              :error="!!validationErrors.file"
+              :error-message="validationErrors.file"
               label="Arquivo"
               outlined
               accept="image/*,video/*,.pdf"
@@ -24,7 +29,8 @@
               </template>
             </q-file>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn outline color="primary" label="Cancelar" @click="onDialogCancel" />
@@ -57,7 +63,7 @@ const formRef = ref(null);
 const selectedFile = ref(null);
 const form = ref({ name: "" });
 
-const { loading, execute } = useSubmitHandler({
+const { loading, validationErrors, execute } = useSubmitHandler({
   formRef,
   onSuccess: (result) => onDialogOK(result),
 });

+ 11 - 5
src/pages/students/components/EditStudentDialog.vue

@@ -1,12 +1,13 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 100%; max-width: 1400px"
     >
       <DefaultDialogHeader title="Dados do Aluno" @close="onDialogCancel" />
 
-      <q-card-section class="q-pt-sm" style="height: 65vh; overflow-y: auto">
+      <q-scroll-area class="dialog-form-scroll" style="height: 65vh">
+        <q-card-section class="q-pt-sm">
         <CustomTabComponent
           v-model:active-tab="currentTab"
           :tabs="tabs"
@@ -14,7 +15,11 @@
         />
 
         <div v-show="currentTab === 'profile'">
-          <StudentDataTab ref="studentDataTabRef" :student="props.student" />
+          <StudentDataTab
+            ref="studentDataTabRef"
+            :student="props.student"
+            :validation-errors="validationErrors"
+          />
         </div>
 
         <div v-show="currentTab === 'responsible'">
@@ -32,7 +37,8 @@
         <div v-show="currentTab === 'media'">
           <MediaTab ref="mediaTabRef" :student-id="props.student.id" />
         </div>
-      </q-card-section>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-separator />
 
@@ -107,7 +113,7 @@ watch(currentTab, (tab) => {
   if (tab === "media") mediaTabRef.value?.refresh();
 });
 
-const { loading: saving, execute } = useSubmitHandler({
+const { loading: saving, validationErrors, execute } = useSubmitHandler({
   onSuccess: () => onDialogOK(true),
 });
 

+ 10 - 4
src/pages/students/components/EditStudentMediaDialog.vue

@@ -1,19 +1,24 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin" style="width: 480px; max-width: 95vw">
+    <q-card class="q-dialog-plugin dialog-form-card" style="width: 480px; max-width: 95vw">
       <DefaultDialogHeader title="Editar Mídia" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onSubmit">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="q-pt-none">
           <div class="column q-gutter-sm">
             <DefaultInput
               v-model="form.name"
+              :error="!!validationErrors.name"
+              :error-message="validationErrors.name"
               label="Nome do documento"
               :rules="[inputRules.required]"
             />
 
             <q-file
               v-model="selectedFile"
+              :error="!!validationErrors.file"
+              :error-message="validationErrors.file"
               label="Arquivo (opcional)"
               outlined
               accept="image/*,video/*,.pdf"
@@ -24,7 +29,8 @@
               </template>
             </q-file>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn outline color="primary" label="Cancelar" @click="onDialogCancel" />
@@ -57,7 +63,7 @@ const formRef = ref(null);
 const selectedFile = ref(null);
 const form = ref({ name: media.name ?? "" });
 
-const { loading, execute } = useSubmitHandler({
+const { loading, validationErrors, execute } = useSubmitHandler({
   formRef,
   onSuccess: (result) => onDialogOK(result),
 });

+ 5 - 3
src/pages/students/components/FreezeContractDialog.vue

@@ -1,9 +1,10 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin overflow-hidden" style="width: 100%; max-width: 860px">
+    <q-card class="q-dialog-plugin dialog-form-card" style="width: 100%; max-width: 860px">
       <DefaultDialogHeader title="Trancar Contrato" @close="onDialogCancel" />
 
-      <q-card-section class="q-pt-sm" style="max-height: 70vh; overflow-y: auto">
+      <q-scroll-area class="dialog-form-scroll">
+        <q-card-section class="q-pt-sm">
 
         <!-- Seleção de meses -->
         <div class="row q-col-gutter-sm q-mb-md items-center">
@@ -70,7 +71,8 @@
           </template>
         </q-table>
 
-      </q-card-section>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-separator />
 

+ 40 - 7
src/pages/students/components/ResponsibleDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 100%; max-width: 900px"
     >
       <DefaultDialogHeader
@@ -9,11 +9,14 @@
         @close="onDialogCancel"
       />
 
-      <q-card-section class="q-pt-sm" style="height: 65vh; overflow-y: auto">
-        <DefaultForm ref="formRef">
+      <q-scroll-area class="dialog-form-scroll" style="height: 65vh">
+        <q-card-section class="q-pt-sm">
+          <DefaultForm ref="formRef">
           <div class="row q-col-gutter-sm">
             <DefaultInput
               v-model="form.name"
+              :error="!!validationErrors.name"
+              :error-message="validationErrors.name"
               label="Nome"
               class="col-6"
               :rules="[inputRules.required]"
@@ -21,6 +24,8 @@
 
             <DefaultInput
               v-model="form.degree"
+              :error="!!validationErrors.degree"
+              :error-message="validationErrors.degree"
               label="Grau de Parentesco"
               class="col-6"
               :rules="[inputRules.required]"
@@ -28,6 +33,8 @@
 
             <DefaultInputDatePicker
               v-model="form.birth_date"
+              :error="!!validationErrors.birth_date"
+              :error-message="validationErrors.birth_date"
               label="Data de Nascimento"
               class="col-4"
               :rules="[inputRules.required]"
@@ -35,14 +42,18 @@
 
             <DefaultInput
               v-model="form.cpf"
+              :error="!!validationErrors.cpf"
+              :error-message="validationErrors.cpf"
               label="CPF"
               class="col-4"
               :mask="masks.Brasil.cpf"
-              :rules="[inputRules.required]"
+              :rules="[inputRules.required, inputRules.cpf]"
             />
 
             <DefaultSelect
               v-model="form.gender"
+              :error="!!validationErrors.gender"
+              :error-message="validationErrors.gender"
               label="Gênero"
               class="col-4"
               emit-value
@@ -52,6 +63,8 @@
 
             <DefaultInput
               v-model="form.email"
+              :error="!!validationErrors.email"
+              :error-message="validationErrors.email"
               label="E-mail"
               class="col-6"
               type="email"
@@ -60,6 +73,8 @@
 
             <DefaultInput
               v-model="form.phone"
+              :error="!!validationErrors.phone"
+              :error-message="validationErrors.phone"
               label="Telefone"
               class="col-6"
               :mask="masks.Brasil.celular"
@@ -68,6 +83,8 @@
 
             <DefaultCepInput
               v-model="form.postal_code"
+              :error="!!validationErrors.postal_code"
+              :error-message="validationErrors.postal_code"
               class="col-4"
               @rua="(v) => (form.street = v)"
               @bairro="(v) => (form.neighborhood = v)"
@@ -77,18 +94,24 @@
 
             <DefaultInput
               v-model="form.street"
+              :error="!!validationErrors.street"
+              :error-message="validationErrors.street"
               label="Endereço"
               class="col-5"
             />
 
             <DefaultInput
               v-model="form.address_number"
+              :error="!!validationErrors.address_number"
+              :error-message="validationErrors.address_number"
               label="Número"
               class="col-3"
             />
 
             <DefaultInput
               v-model="form.neighborhood"
+              :error="!!validationErrors.neighborhood"
+              :error-message="validationErrors.neighborhood"
               label="Bairro"
               class="col-4"
             />
@@ -96,6 +119,8 @@
             <CitySelect
               ref="citySelectRef"
               v-model="selectedCity"
+              :error="!!validationErrors.city_id"
+              :error-message="validationErrors.city_id"
               label="Cidade"
               class="col-4"
               :state="selectedState"
@@ -105,6 +130,8 @@
             <StateSelect
               ref="stateSelectRef"
               v-model="selectedState"
+              :error="!!validationErrors.state_id"
+              :error-message="validationErrors.state_id"
               label="Estado"
               class="col-4"
               :initial-id="props.responsible?.state_id ?? null"
@@ -112,12 +139,16 @@
 
             <DefaultInput
               v-model="form.complement"
+              :error="!!validationErrors.complement"
+              :error-message="validationErrors.complement"
               label="Complemento"
               class="col-12"
             />
 
             <DefaultInput
               v-model="form.notes"
+              :error="!!validationErrors.notes"
+              :error-message="validationErrors.notes"
               label="Observações"
               class="col-12"
               type="textarea"
@@ -125,8 +156,9 @@
               autogrow
             />
           </div>
-        </DefaultForm>
-      </q-card-section>
+          </DefaultForm>
+        </q-card-section>
+      </q-scroll-area>
 
       <q-separator />
 
@@ -246,7 +278,8 @@ watch(selectedCity, (city) => {
   form.value.city_id = city?.value ?? null;
 });
 
-const { loading: saving, execute } = useSubmitHandler({
+const { loading: saving, validationErrors, execute } = useSubmitHandler({
+  formRef,
   onSuccess: () => onDialogOK(true),
 });
 

+ 37 - 0
src/pages/students/tabs/StudentDataTab.vue

@@ -7,6 +7,8 @@
     <div class="row q-col-gutter-sm">
       <DefaultInput
         v-model="form.name"
+        :error="!!validationErrors.name"
+        :error-message="validationErrors.name"
         label="Nome do Aluno"
         class="col-6"
         :rules="[inputRules.required]"
@@ -14,19 +16,26 @@
 
       <DefaultInputDatePicker
         v-model="form.birthdate"
+        :error="!!validationErrors.birth_date"
+        :error-message="validationErrors.birth_date"
         label="Data de Nascimento"
         class="col-6"
       />
 
       <DefaultInput
         v-model="form.cpf"
+        :error="!!validationErrors.document_number"
+        :error-message="validationErrors.document_number"
         label="CPF / CNH"
         class="col-6"
         :mask="masks.Brasil.cpf"
+        :rules="[inputRules.cpf]"
       />
 
       <DefaultSelect
         v-model="form.gender"
+        :error="!!validationErrors.gender"
+        :error-message="validationErrors.gender"
         label="Gênero"
         class="col-6"
         emit-value
@@ -36,6 +45,8 @@
 
       <DefaultInput
         v-model="form.email"
+        :error="!!validationErrors.email"
+        :error-message="validationErrors.email"
         label="E-mail"
         class="col-6"
         type="email"
@@ -43,6 +54,8 @@
 
       <DefaultInput
         v-model="form.phone"
+        :error="!!validationErrors.phone"
+        :error-message="validationErrors.phone"
         label="Celular com DDD"
         class="col-6"
         :mask="masks.Brasil.celular"
@@ -50,6 +63,8 @@
 
       <DefaultCepInput
         v-model="form.cep"
+        :error="!!validationErrors.postal_code"
+        :error-message="validationErrors.postal_code"
         class="col-4"
         @rua="(v) => (form.address = v)"
         @bairro="(v) => (form.neighborhood = v)"
@@ -59,18 +74,24 @@
 
       <DefaultInput
         v-model="form.address"
+        :error="!!validationErrors.street"
+        :error-message="validationErrors.street"
         label="Endereço"
         class="col-5"
       />
 
       <DefaultInput
         v-model="form.address_number"
+        :error="!!validationErrors.address_number"
+        :error-message="validationErrors.address_number"
         label="Número"
         class="col-3"
       />
 
       <DefaultInput
         v-model="form.neighborhood"
+        :error="!!validationErrors.neighborhood"
+        :error-message="validationErrors.neighborhood"
         label="Bairro"
         class="col-4"
       />
@@ -78,6 +99,8 @@
       <CitySelect
         ref="citySelectRef"
         v-model="selectedCity"
+        :error="!!validationErrors.city_id"
+        :error-message="validationErrors.city_id"
         label="Cidade"
         class="col-4"
         :state="selectedState"
@@ -87,6 +110,8 @@
       <StateSelect
         ref="stateSelectRef"
         v-model="selectedState"
+        :error="!!validationErrors.state_id"
+        :error-message="validationErrors.state_id"
         label="Estado"
         class="col-4"
         :initial-id="props.student.state_id"
@@ -94,18 +119,24 @@
 
       <DefaultInput
         v-model="form.complement"
+        :error="!!validationErrors.complement"
+        :error-message="validationErrors.complement"
         label="Complemento"
         class="col-6"
       />
 
       <DefaultInput
         v-model="form.payer"
+        :error="!!validationErrors.payer_name"
+        :error-message="validationErrors.payer_name"
         label="Pagador"
         class="col-6"
       />
 
       <DefaultSelect
         v-model="form.how_found"
+        :error="!!validationErrors.how_did_you_know_us"
+        :error-message="validationErrors.how_did_you_know_us"
         label="Como nos conheceu?"
         class="col-12"
         emit-value
@@ -115,6 +146,8 @@
 
       <DefaultInput
         v-model="form.notes"
+        :error="!!validationErrors.notes"
+        :error-message="validationErrors.notes"
         label="Observações"
         class="col-12"
         type="textarea"
@@ -144,6 +177,10 @@ const props = defineProps({
     type: Object,
     required: true,
   },
+  validationErrors: {
+    type: Object,
+    default: () => ({}),
+  },
 });
 
 const { inputRules } = useInputRules();

+ 5 - 3
src/pages/support/components/AddEditReplyDialog.vue

@@ -1,21 +1,23 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 600px">
-      <q-card class="overflow-hidden" style="width: 100%">
+      <q-card class="dialog-form-card" style="width: 100%">
         <DefaultDialogHeader
           :title="replyItem ? 'Editar Comentário' : 'Responder Suporte'"
           @close="onDialogCancel"
         />
 
         <DefaultForm ref="formRef" @submit="onOKClick">
-          <q-card-section class="q-pt-sm">
+          <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+            <q-card-section class="q-pt-sm">
             <DefaultInput
               v-model="replyText"
               label="Descreva a resposta"
               type="textarea"
               class="col-12"
             />
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-px-md q-pb-md">
             <q-btn

+ 5 - 6
src/pages/support/components/AddEditTicketDialog.vue

@@ -1,7 +1,7 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 1100px">
-      <q-card style="height: 500px; display: flex; flex-direction: column; overflow: hidden">
+      <q-card class="dialog-form-card" style="height: 500px">
         <DefaultDialogHeader
           :title="ticket ? 'Editar Ticket' : 'Novo Ticket'"
           @close="onDialogCancel"
@@ -17,7 +17,8 @@
           "
           @submit="onOKClick"
         >
-          <q-card-section class="q-pt-sm" style="flex: 1; overflow-y: auto; min-height: 0">
+          <q-scroll-area class="dialog-form-scroll">
+            <q-card-section class="q-pt-sm">
             <CustomTabComponent
               v-if="ticket?.id"
               v-model:active-tab="currentTab"
@@ -90,9 +91,6 @@
               </div>
               <div
                 style="
-                  height: 100%;
-                  max-height: 340px;
-                  overflow-y: auto;
                   display: flex;
                   flex-direction: column;
                   gap: 8px;
@@ -117,7 +115,8 @@
                 </div>
               </div>
             </div>
-          </q-card-section>
+            </q-card-section>
+          </q-scroll-area>
 
           <q-card-actions align="right" class="q-px-md q-pb-md" style="flex-shrink: 0">
             <q-btn

+ 5 - 3
src/pages/support/components/CloseTicketDialog.vue

@@ -1,10 +1,11 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <div style="width: 100%; max-width: 500px">
-      <q-card class="overflow-hidden" style="width: 100%">
+      <q-card class="dialog-form-card" style="width: 100%">
         <DefaultDialogHeader title="Encerrar Ticket" @close="onDialogCancel" />
 
-        <q-card-section class="q-pt-sm column q-gutter-y-md">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="q-pt-sm column q-gutter-y-md">
           <DefaultSelect
             v-model="resolved"
             label="A solicitação foi resolvida?"
@@ -21,7 +22,8 @@
               Sua solicitação não foi resolvida, finalize o suporte.
             </template>
           </p>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-px-md q-pb-md">
           <q-btn

+ 7 - 4
src/pages/unit/components/AddEditHistoryDialog.vue

@@ -1,17 +1,19 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 560px; max-width: 95vw"
     >
       <DefaultDialogHeader :title="dialogTitle" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="q-pt-none">
           <div class="column q-gutter-sm">
             <DefaultInput
               v-model="form.title"
-              v-model:error="validationErrors.title"
+              :error="!!validationErrors.title"
+              :error-message="validationErrors.title"
               label="Título"
               outlined
               :rules="[inputRules.required]"
@@ -35,7 +37,8 @@
               label="Visível ao franqueado"
             />
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn

+ 43 - 22
src/pages/unit/components/AddEditPartnerDialog.vue

@@ -1,13 +1,14 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 860px; max-width: 95vw"
     >
       <DefaultDialogHeader :title="dialogTitle" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll">
+          <q-card-section class="q-pt-none">
           <div class="column items-center q-mb-md">
             <AvatarImageComponent
               ref="avatarRef"
@@ -18,7 +19,8 @@
           <div class="row q-col-gutter-sm">
             <DefaultInput
               v-model="form.name"
-              v-model:error="validationErrors.name"
+              :error="!!validationErrors.name"
+              :error-message="validationErrors.name"
               class="col-8"
               label="Nome completo"
               outlined
@@ -27,7 +29,8 @@
 
             <DefaultInput
               v-model="form.role"
-              v-model:error="validationErrors.role"
+              :error="!!validationErrors.role"
+              :error-message="validationErrors.role"
               class="col-4"
               label="Função"
               outlined
@@ -35,7 +38,8 @@
 
             <DefaultInput
               v-model="form.social_name"
-              v-model:error="validationErrors.social_name"
+              :error="!!validationErrors.social_name"
+              :error-message="validationErrors.social_name"
               class="col-6"
               label="Nome social"
               outlined
@@ -43,17 +47,19 @@
 
             <DefaultInput
               v-model="form.cpf"
-              v-model:error="validationErrors.cpf"
+              :error="!!validationErrors.cpf"
+              :error-message="validationErrors.cpf"
               class="col-3"
               label="CPF"
               outlined
               :mask="masks.Brasil.cpf"
-              :rules="[inputRules.required]"
+              :rules="[inputRules.required, inputRules.cpf]"
             />
 
             <DefaultInput
               v-model="form.rg"
-              v-model:error="validationErrors.rg"
+              :error="!!validationErrors.rg"
+              :error-message="validationErrors.rg"
               class="col-3"
               label="RG"
               outlined
@@ -61,7 +67,8 @@
 
             <DefaultInput
               v-model="birthDateDisplay"
-              v-model:error="validationErrors.birth_date"
+              :error="!!validationErrors.birth_date"
+              :error-message="validationErrors.birth_date"
               class="col-3"
               label="Data de Nascimento"
               placeholder="DD/MM/AAAA"
@@ -71,7 +78,8 @@
 
             <DefaultInput
               v-model="form.participation"
-              v-model:error="validationErrors.participation"
+              :error="!!validationErrors.participation"
+              :error-message="validationErrors.participation"
               class="col-3"
               label="Participação (%)"
               max="100"
@@ -82,7 +90,8 @@
 
             <DefaultInput
               v-model="form.email"
-              v-model:error="validationErrors.email"
+              :error="!!validationErrors.email"
+              :error-message="validationErrors.email"
               class="col-6"
               label="E-mail"
               outlined
@@ -91,7 +100,8 @@
 
             <DefaultInput
               v-model="form.secondary_email"
-              v-model:error="validationErrors.secondary_email"
+              :error="!!validationErrors.secondary_email"
+              :error-message="validationErrors.secondary_email"
               class="col-6"
               label="E-mail Secundário"
               outlined
@@ -100,7 +110,8 @@
 
             <DefaultInput
               v-model="form.phone_number"
-              v-model:error="validationErrors.phone_number"
+              :error="!!validationErrors.phone_number"
+              :error-message="validationErrors.phone_number"
               class="col-6"
               label="Telefone"
               outlined
@@ -109,16 +120,19 @@
 
             <DefaultInput
               v-model="form.cell_number"
-              v-model:error="validationErrors.cell_number"
+              :error="!!validationErrors.cell_number"
+              :error-message="validationErrors.cell_number"
               class="col-6"
               label="Celular"
               outlined
               :mask="masks.Brasil.celular"
+              :rules="[inputRules.required]"
             />
 
             <DefaultCepInput
               v-model="form.postal_code"
-              v-model:error="validationErrors.postal_code"
+              :error="!!validationErrors.postal_code"
+              :error-message="validationErrors.postal_code"
               class="col-6"
               outlined
               @rua="form.street = $event"
@@ -129,7 +143,8 @@
 
             <DefaultInput
               v-model="form.street"
-              v-model:error="validationErrors.street"
+              :error="!!validationErrors.street"
+              :error-message="validationErrors.street"
               class="col-6"
               label="Endereço"
               outlined
@@ -137,7 +152,8 @@
 
             <DefaultInput
               v-model="form.address_number"
-              v-model:error="validationErrors.address_number"
+              :error="!!validationErrors.address_number"
+              :error-message="validationErrors.address_number"
               class="col-6"
               label="Número"
               outlined
@@ -145,7 +161,8 @@
 
             <DefaultInput
               v-model="form.neighborhood"
-              v-model:error="validationErrors.neighborhood"
+              :error="!!validationErrors.neighborhood"
+              :error-message="validationErrors.neighborhood"
               class="col-4"
               label="Bairro"
               outlined
@@ -154,7 +171,8 @@
             <StateSelect
               ref="stateSelectRef"
               v-model="selectedState"
-              v-model:error="validationErrors.state_id"
+              :error="!!validationErrors.state_id"
+              :error-message="validationErrors.state_id"
               class="col-4"
               label="Estado"
               outlined
@@ -163,7 +181,8 @@
             <CitySelect
               ref="citySelectRef"
               v-model="selectedCity"
-              v-model:error="validationErrors.city_id"
+              :error="!!validationErrors.city_id"
+              :error-message="validationErrors.city_id"
               class="col-4"
               label="Cidade"
               outlined
@@ -172,13 +191,15 @@
 
             <DefaultInput
               v-model="form.complement"
-              v-model:error="validationErrors.complement"
+              :error="!!validationErrors.complement"
+              :error-message="validationErrors.complement"
               class="col-12"
               label="Complemento"
               outlined
             />
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions>
           <q-space />

+ 7 - 4
src/pages/unit/components/AddMediaDialog.vue

@@ -1,17 +1,19 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card
-      class="q-dialog-plugin overflow-hidden"
+      class="q-dialog-plugin dialog-form-card"
       style="width: 480px; max-width: 95vw"
     >
       <DefaultDialogHeader title="Adicionar Mídia" @close="onDialogCancel" />
 
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="q-pt-none">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--xs">
+          <q-card-section class="q-pt-none">
           <div class="column q-gutter-sm">
             <DefaultInput
               v-model="form.title"
-              v-model:error="validationErrors.title"
+              :error="!!validationErrors.title"
+              :error-message="validationErrors.title"
               label="Título"
               outlined
               :rules="[inputRules.required]"
@@ -32,7 +34,8 @@
               </template>
             </q-file>
           </div>
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
 
         <q-card-actions align="right" class="q-pa-md">
           <q-btn

+ 39 - 2
src/pages/unit/tabs/UnitDataTab.vue

@@ -7,6 +7,8 @@
         <div class="row full-width q-mt-md q-col-gutter-sm">
           <DefaultInput
             :model-value="form.name"
+            :error="!!validationErrors.name"
+            :error-message="validationErrors.name"
             class="col-12"
             disable
             label="Nome da Unidade"
@@ -15,6 +17,8 @@
 
           <DefaultInput
             :model-value="form.social_reason"
+            :error="!!validationErrors.social_reason"
+            :error-message="validationErrors.social_reason"
             class="col-12"
             disable
             label="Razão Social"
@@ -23,6 +27,8 @@
 
           <DefaultInput
             :model-value="form.fantasy_name"
+            :error="!!validationErrors.fantasy_name"
+            :error-message="validationErrors.fantasy_name"
             class="col-12"
             disable
             label="Nome Fantasia"
@@ -31,6 +37,8 @@
 
           <DefaultInput
             :model-value="form.cnpj"
+            :error="!!validationErrors.cnpj"
+            :error-message="validationErrors.cnpj"
             class="col-4"
             disable
             label="CNPJ"
@@ -39,6 +47,8 @@
 
           <DefaultInput
             :model-value="form.state_registration"
+            :error="!!validationErrors.state_registration"
+            :error-message="validationErrors.state_registration"
             class="col-4"
             disable
             label="Inscrição Estadual"
@@ -47,6 +57,8 @@
 
           <DefaultInput
             :model-value="form.name_responsible"
+            :error="!!validationErrors.name_responsible"
+            :error-message="validationErrors.name_responsible"
             class="col-4"
             disable
             label="Responsável"
@@ -55,6 +67,8 @@
 
           <DefaultInput
             :model-value="form.postal_code"
+            :error="!!validationErrors.postal_code"
+            :error-message="validationErrors.postal_code"
             class="col-3"
             disable
             label="CEP"
@@ -63,6 +77,8 @@
 
           <DefaultInput
             :model-value="form.street"
+            :error="!!validationErrors.street"
+            :error-message="validationErrors.street"
             class="col-6"
             disable
             label="Endereço"
@@ -71,6 +87,8 @@
 
           <DefaultInput
             :model-value="form.address_number"
+            :error="!!validationErrors.address_number"
+            :error-message="validationErrors.address_number"
             class="col-3"
             disable
             label="Número"
@@ -79,6 +97,8 @@
 
           <DefaultInput
             :model-value="form.neighborhood"
+            :error="!!validationErrors.neighborhood"
+            :error-message="validationErrors.neighborhood"
             class="col-4"
             disable
             label="Bairro"
@@ -87,6 +107,8 @@
 
           <DefaultInput
             :model-value="stateName"
+            :error="!!validationErrors.state_id"
+            :error-message="validationErrors.state_id"
             class="col-4"
             disable
             label="Estado"
@@ -95,6 +117,8 @@
 
           <DefaultInput
             :model-value="cityName"
+            :error="!!validationErrors.city_id"
+            :error-message="validationErrors.city_id"
             class="col-4"
             disable
             label="Cidade"
@@ -103,6 +127,8 @@
 
           <DefaultInput
             :model-value="form.complement"
+            :error="!!validationErrors.complement"
+            :error-message="validationErrors.complement"
             class="col-12"
             disable
             label="Complemento"
@@ -111,6 +137,8 @@
 
           <DefaultInput
             :model-value="form.email"
+            :error="!!validationErrors.email"
+            :error-message="validationErrors.email"
             class="col-6"
             disable
             label="E-mail Principal"
@@ -119,6 +147,8 @@
 
           <DefaultInput
             :model-value="form.secondary_email"
+            :error="!!validationErrors.secondary_email"
+            :error-message="validationErrors.secondary_email"
             class="col-6"
             disable
             label="E-mail Administrativo"
@@ -127,6 +157,8 @@
 
           <DefaultInput
             :model-value="form.phone_number"
+            :error="!!validationErrors.phone_number"
+            :error-message="validationErrors.phone_number"
             class="col-6"
             disable
             label="Telefone"
@@ -135,10 +167,13 @@
 
           <DefaultInput
             :model-value="form.cell_number"
+            :error="!!validationErrors.cell_number"
+            :error-message="validationErrors.cell_number"
             class="col-6"
             disable
             label="Celular"
             outlined
+            :rules="[inputRules.required]"
           />
 
           <div class="col-12 q-mt-sm">
@@ -147,7 +182,8 @@
 
           <DefaultPasswordInput
             v-model="password"
-            v-model:error="validationErrors.password"
+            :error="!!validationErrors.password"
+            :error-message="validationErrors.password"
             class="col-6"
             label="Nova Senha"
             outlined
@@ -156,7 +192,8 @@
 
           <DefaultPasswordInput
             v-model="passwordConfirmation"
-            v-model:error="validationErrors.password_confirmation"
+            :error="!!validationErrors.password_confirmation"
+            :error-message="validationErrors.password_confirmation"
             class="col-6"
             label="Confirmar Nova Senha"
             outlined

+ 13 - 1
src/pages/users/UserActionPage.vue

@@ -14,6 +14,8 @@
         <div class="row q-col-gutter-sm">
           <DefaultInput
             v-model="form.name"
+            :error="!!validationErrors.name"
+            :error-message="validationErrors.name"
             label="Nome completo"
             class="col-6"
             outlined
@@ -22,6 +24,8 @@
 
           <DefaultInput
             v-model="form.cpf"
+            :error="!!validationErrors.cpf"
+            :error-message="validationErrors.cpf"
             label="CPF"
             class="col-6"
             outlined
@@ -31,6 +35,8 @@
 
           <UserTypeSelect
             v-model="form.user_type"
+            :error="!!validationErrors.user_type"
+            :error-message="validationErrors.user_type"
             class="col-6"
             outlined
             label="Função"
@@ -41,6 +47,8 @@
 
           <DefaultInput
             v-model="form.phone"
+            :error="!!validationErrors.phone"
+            :error-message="validationErrors.phone"
             label="Telefone"
             class="col-6"
             outlined
@@ -49,6 +57,8 @@
 
           <DefaultInput
             v-model="form.email"
+            :error="!!validationErrors.email"
+            :error-message="validationErrors.email"
             label="E-mail"
             class="col-6"
             outlined
@@ -58,6 +68,8 @@
 
           <DefaultPasswordInput
             v-model="form.password"
+            :error="!!validationErrors.password"
+            :error-message="validationErrors.password"
             label="Senha"
             class="col-6"
             outlined
@@ -139,7 +151,7 @@ const form = ref({
   password: null,
 });
 
-const { loading, execute } = useSubmitHandler({
+const { loading, validationErrors, execute } = useSubmitHandler({
   formRef,
   onSuccess: () => router.push({ name: "UsersPage" }),
 });

+ 15 - 7
src/pages/users/components/AddEditUserDialog.vue

@@ -1,12 +1,14 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="q-dialog-plugin overflow-hidden" style="width: 800px">
+    <q-card class="q-dialog-plugin dialog-form-card" style="width: 800px">
       <DefaultDialogHeader :title="title" @close="onDialogCancel" />
       <DefaultForm ref="formRef" @submit="onOKClick">
-        <q-card-section class="row q-col-gutter-sm q-pt-none">
+        <q-scroll-area class="dialog-form-scroll dialog-form-scroll--sm">
+          <q-card-section class="row q-col-gutter-sm q-pt-none">
           <DefaultInput
             v-model="form.name"
-            v-model:error="validationErrors.name"
+            :error="!!validationErrors.name"
+            :error-message="validationErrors.name"
             :rules="[inputRules.required]"
             :label="$t('common.terms.name')"
             :placeholder="$t('user.profile.name_and_surname')"
@@ -14,7 +16,8 @@
           />
           <UserTypeSelect
             v-model="selectedUserType"
-            v-model:error="validationErrors.type"
+            :error="!!validationErrors.type"
+            :error-message="validationErrors.type"
             :rules="[inputRules.required]"
             :type="form.type"
             :label="$t('common.ui.misc.type')"
@@ -23,7 +26,8 @@
           />
           <DefaultInput
             v-model="form.email"
-            v-model:error="validationErrors.email"
+            :error="!!validationErrors.email"
+            :error-message="validationErrors.email"
             :rules="[inputRules.email, inputRules.required]"
             label="Email"
             :placeholder="'Ex. email@email.com'"
@@ -31,7 +35,8 @@
           />
           <DefaultPasswordInput
             v-model="form.password"
-            v-model:error="validationErrors.password"
+            :error="!!validationErrors.password"
+            :error-message="validationErrors.password"
             :rules="
               user
                 ? [inputRules.password]
@@ -43,6 +48,8 @@
           />
           <DefaultPasswordInput
             v-model="confirmPassword"
+            :error="!!validationErrors.password_confirmation"
+            :error-message="validationErrors.password_confirmation"
             :rules="
               user
                 ? [inputRules.samePassword(form.password)]
@@ -51,7 +58,8 @@
             :label="$t('auth.confirm_password')"
             class="col-md-6 col-12"
           />
-        </q-card-section>
+          </q-card-section>
+        </q-scroll-area>
         <q-card-actions>
           <q-space />
           <q-btn