Răsfoiți Sursa

feat: add text truncation with tooltip for table cells; update input fields with maxlength attributes

alvesantos 2 săptămâni în urmă
părinte
comite
44e64e52d2

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

@@ -102,6 +102,16 @@
       </q-td>
     </template>
 
+    <template #body-cell="props">
+      <q-td :props="props">
+        {{ truncate(props.value) }}
+
+        <q-tooltip v-if="isTruncated(props.value)">
+          {{ props.value }}
+        </q-tooltip>
+      </q-td>
+    </template>
+
     <template #item="{ cols, row, index }">
       <div class="q-pa-xs col-xs-12 col-sm-6 col-md-4 col-lg-3">
         <q-card
@@ -178,9 +188,16 @@ import { useI18n } from "vue-i18n";
 import { useRouter } from "vue-router";
 import { useQuasar } from "quasar";
 
+import { truncate } from "src/helpers/utils";
+
 import DefaultInput from "./DefaultInput.vue";
 import DefaultSelect from "./DefaultSelect.vue";
 
+const TRUNCATE_LENGTH = 30;
+
+const isTruncated = (value) =>
+  String(value ?? "").length > TRUNCATE_LENGTH;
+
 const emit = defineEmits(["onRowClick", "onAddItem", "noRows"]);
 
 const {
@@ -366,7 +383,9 @@ const usableSlots = (slots) => {
   const availableSlots = Object.keys(slots);
   const usableSlots = availableSlots.filter(
     (slot) =>
-      !["body-cell-actions", "top", "loading", "no-data"].includes(slot),
+      !["body-cell-actions", "body-cell", "top", "loading", "no-data"].includes(
+        slot,
+      ),
   );
   return usableSlots;
 };

+ 17 - 0
src/components/defaults/DefaultTableServerSide.vue

@@ -82,6 +82,16 @@
       </q-td>
     </template>
 
+    <template #body-cell="props">
+      <q-td :props="props">
+        {{ truncate(props.value) }}
+
+        <q-tooltip v-if="isTruncated(props.value)">
+          {{ props.value }}
+        </q-tooltip>
+      </q-td>
+    </template>
+
     <template v-if="!hideNoDataLabel" #no-data>
       <div class="q-my-md row justify-center full-width">
         <q-spinner v-if="loading" color="primary" size="30px" />
@@ -154,9 +164,16 @@ import { ref, computed, onMounted, toRaw, watch } from "vue";
 import { useI18n } from "vue-i18n";
 import { useRouter } from "vue-router";
 
+import { truncate } from "src/helpers/utils";
+
 import DefaultInput from "./DefaultInput.vue";
 import DefaultSelect from "./DefaultSelect.vue";
 
+const TRUNCATE_LENGTH = 30;
+
+const isTruncated = (value) =>
+  String(value ?? "").length > TRUNCATE_LENGTH;
+
 const emit = defineEmits([
   "onRowClick",
   "onAddItem",

+ 0 - 15
src/css/table.scss

@@ -31,21 +31,6 @@
     &.q-table--loading thead tr:last-child th {
       top: 48px;
     }
-
-    // Trunca texto sem espaços (hashes, URLs, etc.) para não estourar a
-    // largura da coluna e desalinhar o cabeçalho sticky.
-    td,
-    th {
-      max-width: 260px;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-    }
-
-    td.q-table--col-auto-width {
-      max-width: none;
-      overflow: visible;
-    }
   }
 
   tr {

+ 11 - 11
src/helpers/utils.js

@@ -1,17 +1,17 @@
 import { useI18n } from "vue-i18n";
 
 /**
- * @description Corta uma string em um determinado tamanho.
- * @param {string} string string a ser cortada.
- * @param {string} size tamanho da string.
- * @returns {string} string cortada.
+ * @description Corta um valor em um determinado tamanho, adicionando "..." no final.
+ * @param {*} value valor a ser cortado (convertido para string).
+ * @param {number} maxLength tamanho máximo antes de truncar.
+ * @returns {string} string truncada.
  */
-const excerpt = (string, size = 30) => {
-  if (size == null) return string;
-  if (string.length > size) {
-    string = string.substring(0, size) + "...";
-  }
-  return string;
+const truncate = (value, maxLength = 40) => {
+  const text = value == null ? "" : String(value);
+
+  if (text.length <= maxLength) return text;
+
+  return `${text.slice(0, maxLength)}...`;
 };
 
 /**
@@ -123,7 +123,7 @@ const formatUnitName = (unit, fallback = "—") =>
 export {
   formatDateDMYtoYMD,
   formatDateYMDtoDMY,
-  excerpt,
+  truncate,
   convertDateTime,
   formatToBRLCurrency,
   normalizeString,

+ 11 - 0
src/pages/unit/tabs/UnitDataTab.vue

@@ -11,6 +11,7 @@
               v-model:error="validationErrors.name"
               class="col-12"
               label="Nome da Unidade"
+              maxlength="100"
               outlined
               :error-message="validationErrors.name"
               :rules="[inputRules.required]"
@@ -21,6 +22,7 @@
               v-model:error="validationErrors.social_reason"
               class="col-12"
               label="Razão Social"
+              maxlength="100"
               outlined
               :error-message="validationErrors.social_reason"
               :rules="[inputRules.required]"
@@ -31,6 +33,7 @@
               v-model:error="validationErrors.fantasy_name"
               class="col-12"
               label="Nome Fantasia"
+              maxlength="100"
               outlined
               :error-message="validationErrors.fantasy_name"
             />
@@ -51,6 +54,7 @@
               v-model:error="validationErrors.state_registration"
               class="col-4"
               label="Inscrição Estadual"
+              maxlength="50"
               outlined
               :error-message="validationErrors.state_registration"
             />
@@ -60,6 +64,7 @@
               v-model:error="validationErrors.name_responsible"
               class="col-4"
               label="Franqueado Operador"
+              maxlength="100"
               outlined
               :error-message="validationErrors.name_responsible"
               :rules="[inputRules.required]"
@@ -83,6 +88,7 @@
               v-model:error="validationErrors.street"
               class="col-6"
               label="Endereço"
+              maxlength="100"
               outlined
               :error-message="validationErrors.street"
               :rules="[inputRules.required]"
@@ -93,6 +99,7 @@
               v-model:error="validationErrors.address_number"
               class="col-3"
               label="Número"
+              maxlength="20"
               outlined
               :error-message="validationErrors.address_number"
               :rules="[inputRules.required]"
@@ -103,6 +110,7 @@
               v-model:error="validationErrors.neighborhood"
               class="col-4"
               label="Bairro"
+              maxlength="100"
               outlined
               :error-message="validationErrors.neighborhood"
               :rules="[inputRules.required]"
@@ -136,6 +144,7 @@
               v-model:error="validationErrors.complement"
               class="col-12"
               label="Complemento"
+              maxlength="100"
               outlined
               :error-message="validationErrors.complement"
             />
@@ -145,6 +154,7 @@
               v-model:error="validationErrors.email"
               class="col-6"
               label="E-mail Principal"
+              maxlength="100"
               outlined
               :error-message="validationErrors.email"
               :rules="[inputRules.required, inputRules.email]"
@@ -155,6 +165,7 @@
               v-model:error="validationErrors.secondary_email"
               class="col-6"
               label="E-mail Administrativo"
+              maxlength="100"
               outlined
               :error-message="validationErrors.secondary_email"
               :rules="[inputRules.email]"