Browse Source

refactor: ajustes visuais no dashboard

Gustavo Mantovani 5 days ago
parent
commit
0411db11c8

+ 51 - 32
src/components/dashboard/DashboardHeaderBar.vue

@@ -3,55 +3,75 @@
   <div class="dashboard-header shadow-card bg-white row items-center no-wrap q-px-md q-pb-sm">
     <div class="col column q-gutter-y-xs">
       <div class="row items-center q-gutter-x-xs">
-        <q-icon name="mdi-star" color="warning" size="14px" />
-        <span class="dashboard-metric-value font10 fontregular">{{ data?.rating != null ? Number(data.rating).toFixed(1).replace('.', ',') : '-' }}</span>
-        <span class="dashboard-metric-meta font10 fontregular">({{ data?.total_ratings ?? 0 }})</span>
+        <q-icon
+          color="warning"
+          name="mdi-star"
+          size="14px"
+        />
+
+        <span class="dashboard-metric-value font10 fontregular">
+          {{ data?.rating != null ? Number(data.rating).toFixed(1).replace('.', ',') : '-' }}
+        </span>
+
+        <span class="dashboard-metric-meta font10 fontregular">
+          ({{ data?.total_ratings ?? 0 }})
+        </span>
       </div>
+
       <div class="row items-center q-gutter-x-xs">
-        <q-icon name="mdi-broom" color="secondary" size="14px" />
-        <span class="dashboard-metric-value font10 fontregular">{{ data?.total_services ?? 0 }}</span>
+        <q-icon
+          color="secondary"
+          name="mdi-broom"
+          size="14px"
+        />
+
+        <span class="dashboard-metric-value font10 fontregular">
+          {{ data?.total_services ?? 0 }}
+        </span>
       </div>
     </div>
 
     <div class="col-auto row justify-center">
-      <img :src="LogoDiariaColorida" alt="Diária" class="dashboard-logo" />
+      <img
+        alt="Diária"
+        class="dashboard-logo"
+        :src="LogoDiariaColorida"
+      />
     </div>
 
    <div class="col row justify-end items-center">
 
-  <q-btn
-    flat
-    round
-    dense
-    color="grey-7"
-    size="sm"
-    @click="goToNotifications"
-  >
-
-    <q-icon
-      name="mdi-bell-outline"
-      size="20px"
-    />
-
-    <q-badge
-      v-if="unreadNotifications > 0"
-      floating
-      rounded
-      color="pink"
-      class="notification-badge font10 fontregular"
+    <q-btn
+      color="grey-7"
+      dense
+      flat
+      round
+      size="sm"
+      @click="goToNotifications"
     >
-      {{ unreadNotifications }}
-    </q-badge>
+      <q-icon
+        name="mdi-bell-outline"
+        size="20px"
+      />
 
-  </q-btn>
-
-</div>
+      <q-badge
+        v-if="unreadNotifications > 0"
+        class="notification-badge font10 fontregular"
+        color="pink"
+        floating
+        rounded
+      >
+        {{ unreadNotifications }}
+      </q-badge>
+    </q-btn>
+  </div>
   </div>
 </template>
 
 <script setup>
 import { computed } from 'vue'
 import { useRouter } from 'vue-router'
+
 import LogoDiariaColorida from 'src/assets/logo_diaria_colorido_sem_texto.svg'
 
 const router = useRouter()
@@ -61,7 +81,6 @@ const props = defineProps({
     type: Object,
     default: () => null
   },
-
   notifications: {
     type: Array,
     default: () => []

+ 5 - 12
src/components/dashboard/DashboardNextSchedules.vue

@@ -31,9 +31,9 @@
         <q-card
           v-for="item in data"
           :key="item.id"
+          :class="{ 'schedule-card--single': data.length === 1 }"
           :flat="false"
           class="schedule-card card-border shadow-card bg-surface"
-          :class="{ 'schedule-card--single': data.length === 1 }"
         >
           <q-card-section class="q-pa-sm">
             <div class="row no-wrap items-center q-gutter-x-sm">
@@ -154,9 +154,7 @@
                 <q-icon
                   size="14px"
                   :color="item.offers_meal ? 'secondary' : 'grey-5'"
-                  :name="
-                    item.offers_meal ? 'mdi-silverware' : 'mdi-close-outline'
-                  "
+                  :name="item.offers_meal ? 'mdi-silverware' : 'mdi-close-outline'"
                 />
 
                 <span :class="item.offers_meal ? 'fontmedium' : ''">
@@ -176,7 +174,7 @@
 </template>
 
 <script setup>
-import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
+import { avatarColors, formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
 import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
 import { useI18n } from "vue-i18n";
 import { useQuasar } from "quasar";
@@ -194,12 +192,7 @@ const $q = useQuasar();
 
 const { t } = useI18n();
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
+
 
 const copyAddress = (address) => {
   const formatted = formatAddress(address);
@@ -277,7 +270,7 @@ const formatWeekday = (iso) => {
 }
 .schedule-card {
   border-radius: 12px;
-  min-width: 85vw;
+  min-width: 82vw;
 }
 .schedule-card--single {
   min-width: 100%;

+ 12 - 9
src/components/dashboard/DashboardOpportunities.vue

@@ -36,11 +36,15 @@
     </div>
 
     <div v-else class="scroll-wrapper">
-      <div class="scroll-track row no-wrap q-gutter-x-md">
+      <div
+        class="scroll-track row no-wrap"
+        :class="{ 'q-gutter-x-md': data.length > 1 }"
+      >
         <q-card
           v-for="item in data"
           :key="item.id"
           :flat="false"
+          :class="{ 'opportunity-card--single': data.length === 1 }"
           class="opportunity-card bg-surface shadow-card card-border"
         >
           <q-card-section class="q-pa-sm">
@@ -156,7 +160,7 @@
 </template>
 
 <script setup>
-import { formatCurrency, getFirstName } from "src/helpers/utils";
+import { avatarColors, formatCurrency, getFirstName } from "src/helpers/utils";
 import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
 import { useQuasar } from "quasar";
 import { useRouter } from "vue-router";
@@ -174,12 +178,7 @@ const $q = useQuasar();
 
 const router = useRouter();
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
+
 
 const formatDayMonth = (iso) => {
   if (!iso) {
@@ -247,7 +246,11 @@ const openOpportunityDetails = (item) => {
 }
 .opportunity-card {
   border-radius: 12px;
-  min-width: 85vw;
+  min-width: 82vw;
+}
+.opportunity-card--single {
+  min-width: 100%;
+  width: 100%;
 }
 .text-schedule-date-bold {
   color: #3a3a4a;

+ 9 - 7
src/components/dashboard/DashboardPriceSuggest.vue

@@ -1,9 +1,10 @@
 <template>
   <div class="q-mx-md q-mb-md">
-    <q-card class="price-suggest-card bg-surface shadow-card card-border" :flat="false">
+    <q-card
+      class="price-suggest-card bg-surface shadow-card card-border"
+      :flat="false"
+    >
       <q-card-section class="q-pa-md">
-
-        <!-- Preço médio da região -->
         <div class="row items-center justify-between q-mb-md">
           <div class="column">
             <span class="text-suggest-label font12">
@@ -23,9 +24,7 @@
           </q-badge>
         </div>
 
-        <!-- Minha diária -->
         <div class="row items-center justify-between">
-
           <div class="column">
             <span class="text-suggest-label font12 fontbold">
               {{ $t('provider.dashboard.price_suggest.my_price_label') }}
@@ -68,7 +67,10 @@
                 {{ $t('common.alter') }}
               </span>
 
-              <q-icon name="mdi-pencil-outline" size="xs" />
+              <q-icon
+                name="mdi-pencil-outline"
+                size="xs"
+              />
             </div>
           </q-btn>
         </div>
@@ -81,6 +83,7 @@
 import { formatCurrency } from 'src/helpers/utils';
 import { ref } from 'vue';
 import { useQuasar } from 'quasar';
+
 import ServiceDataDialog from '../profile/ProfileServiceDataDialog.vue';
 
 defineProps({
@@ -100,7 +103,6 @@ const openServiceDataDialog = () => {
     component: ServiceDataDialog
   })
 }
-
 </script>
 
 <style scoped lang="scss">

+ 5 - 1
src/components/dashboard/DashboardScrollAreaSchedules.vue

@@ -7,7 +7,11 @@
         class="promo-card"
         @click="goToCard(card)"
       >
-        <img :alt="card.alt" class="promo-card__img" :src="card.image" />
+        <img
+          :alt="card.alt"
+          :src="card.image"
+          class="promo-card__img"
+        />
       </div>
     </div>
   </section>

+ 5 - 15
src/components/dashboard/DashboardSolicitations.vue

@@ -40,12 +40,6 @@
           <q-card-section class="q-pa-sm solicitation-card-section">
             <div class="items-start no-wrap q-gutter-x-sm row text-text">
               <div class="solicitation-avatar-column">
-                <!--
-                <div class="font9 fontmedium text-text solicitation-time">
-                  {{ formatTimeSinceRequest(item.time_since_request) }}
-                </div>
-                !-->
-
                 <q-avatar size="48px">
                   <img
                     v-if="item.customer_photo"
@@ -183,8 +177,8 @@
 </template>
 
 <script setup>
+import { avatarColors, formatCurrency, getFirstName } from "src/helpers/utils";
 import { computed } from "vue";
-import { formatCurrency, getFirstName } from "src/helpers/utils";
 import { useI18n } from "vue-i18n";
 
 const props = defineProps({
@@ -196,6 +190,8 @@ const props = defineProps({
 
 const emit = defineEmits(["accept", "reject", "view-details"]);
 
+const { t } = useI18n();
+
 const groupedSolicitations = computed(() => {
   const groups = new Map();
 
@@ -203,6 +199,7 @@ const groupedSolicitations = computed(() => {
     const key = item.service_package_id
       ? `package:${item.service_package_id}`
       : `schedule:${item.id}`;
+
     const group = groups.get(key);
 
     if (group) {
@@ -221,14 +218,7 @@ const packageTotal = (item) =>
     0,
   );
 
-const { t } = useI18n();
-
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
+//
 
 const parseLocalDate = (iso) => {
   if (!iso) return null;

+ 42 - 11
src/components/dashboard/DashboardSummaryInfos.vue

@@ -1,22 +1,48 @@
 <template>
-  <q-card class="summary-card shadow-card q-ma-md bg-surface card-border" :flat="false">
+  <q-card
+    class="summary-card shadow-card q-ma-md bg-surface card-border"
+    :flat="false"
+  >
     <q-card-section class="q-pa-md">
       <div class="row items-center no-wrap q-gutter-x-md">
         <div class="row items-center no-wrap q-gutter-x-sm col">
           <q-avatar size="54px" :style="!data?.profile_photo ? avatarStyle : undefined" class="">
-            <img v-if="data?.profile_photo" :src="data.profile_photo" style="object-fit:cover;width:100%;height:100%;border-radius:50%" />
-            <span v-else> {{ getFirstName(data?.name)?.slice(0, 2).toUpperCase() ?? '??' }}</span>
+            <img
+              v-if="data?.profile_photo"
+              :src="data.profile_photo"
+              style="object-fit:cover;width:100%;height:100%;border-radius:50%"
+            />
+
+            <span v-else> {{ getFirstName(data?.name)?.slice(0, 2).toUpperCase() ?? '??' }} </span>
           </q-avatar>
+
           <div class="column q-gutter-y-xs min-width-0">
-            <span class="summary-greeting font14 fontmedium">{{ $t('provider.dashboard.summary.welcome') }}</span>
-            <span class="summary-name text-primary font16 fontbold">{{ getFirstName(data?.name) || $t('provider.dashboard.summary.welcome') }}</span>
+            <span class="summary-greeting font14 fontmedium">
+              {{ $t('provider.dashboard.summary.welcome') }}
+            </span>
+
+            <span class="summary-name text-primary font16 fontbold">
+              {{ getFirstName(data?.name) || $t('provider.dashboard.summary.welcome') }}
+            </span>
           </div>
         </div>
+
         <div class="column items-end q-gutter-y-xs col-auto">
-          <span class="summary-label text-grey-6 font12 fontbold">{{ $t('provider.dashboard.summary.my_schedules') }}</span>
+          <span class="summary-label text-grey-6 font12 fontbold">
+            {{ $t('provider.dashboard.summary.my_schedules') }}
+          </span>
+
           <span class="summary-count row">
-            <q-icon name="mdi-clock-check-outline" class="q-my-auto" size="xs" color="positive" />
-            <span class="q-my-auto q-ml-sm">{{ data?.pending_services ?? 0 }}</span>
+            <q-icon
+              class="q-my-auto"
+              color="positive"
+              name="mdi-clock-check-outline"
+              size="xs"
+            />
+
+            <span class="q-my-auto q-ml-sm">
+              {{ data?.pending_services ?? 0 }}
+            </span>
           </span>
         </div>
       </div>
@@ -34,11 +60,11 @@
 
         <q-btn
           :aria-label="$t(expanded ? 'common.collapse' : 'common.expand')"
+          :icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
           class="col-auto expand-button"
           color="secondary"
           dense
           flat
-          :icon="expanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
           round
           size="sm"
           @click="expanded = !expanded"
@@ -57,20 +83,25 @@ defineProps({ data: { type: Object, default: () => null } });
 const expanded = ref(false);
 
 const avatarStyle = {
-  background: 'linear-gradient(135deg, #ffd7e8 0%, #ff9acc 100%)',
-  color: '#7a154f',
+  background: 'linear-gradient(135deg, #ffd7e8 0%, #ff9acc 100%)', color: '#7a154f',
 };
 
 </script>
 
 <style scoped lang="scss">
 .summary-card { overflow: hidden; }
+
 .min-width-0 { min-width: 0; }
+
 .summary-greeting { color: #8c8c98; display: block; }
+
 .summary-name { display: block; line-height: 1.1; }
+
 .summary-label { white-space: nowrap; }
+
 .summary-count {
   color: #3a3a4a;
 }
+
 .expand-button { margin: -6px -6px -6px 2px; }
 </style>

+ 222 - 117
src/components/dashboard/DashboardTodayServices.vue

@@ -8,15 +8,20 @@
         <q-card
           v-for="item in props.data"
           :key="item.id"
-          class="today-card card-border shadow-card bg-surface"
           :class="{ 'today-card--single': props.data.length === 1 }"
           :flat="false"
+          class="today-card card-border shadow-card bg-surface"
         >
-          <q-card-section class="q-pa-md">
+          <q-card-section class="column justify-between q-pa-md full-height">
             <div class="row no-wrap items-center q-mb-xs">
               <div class="col-8 row">
                 <q-avatar size="40px" class="flex-shrink-0 q-mr-sm">
-                  <img v-if="item.customer_photo" :src="item.customer_photo" style="object-fit:cover" />
+                  <img
+                    v-if="item.customer_photo"
+                    :src="item.customer_photo"
+                    style="object-fit:cover"
+                  />
+
                   <span
                     v-else
                     :style="avatarColors[item.id % avatarColors.length]"
@@ -31,16 +36,30 @@
                   <span class="text-text font12">
                     <template v-if="cardState(item) === 'finished'">
                       {{ $t('provider.dashboard.today_services.finished_label') }}
-                      <span class="fontbold"> {{ ' ' + (getFirstName(item.client_name) || '—') }}</span>
+
+                      <span class="fontbold">
+                        {{ ' ' + (getFirstName(item.client_name) || '—') }}
+                      </span>
+
                       {{ ' ' + $t('provider.dashboard.today_services.finished_suffix') }}
                     </template>
+
                     <template v-else>
                       {{ $t('provider.dashboard.today_services.start_label') }}
-                      <span class="fontbold"> {{ ' ' + (getFirstName(item.client_name) || '—') }}</span>
+
+                      <span class="fontbold">
+                        {{ ' ' + (getFirstName(item.client_name) || '—') }}
+                      </span>
                     </template>
                   </span>
+
                   <div class="row items-center q-mt-xs no-wrap">
-                    <q-icon name="mdi-clock-outline" size="12px" class="q-mr-xs gradient-diarista" />
+                    <q-icon
+                      class="q-mr-xs gradient-diarista"
+                      name="mdi-clock-outline"
+                      size="12px"
+                    />
+
                     <span class="text-grey-5 font9 fontregular">
                       {{ $t('common.from') }} {{  item.start_time?.slice(0, 5) }} {{ $t('common.to') }} {{ item.end_time?.slice(0, 5) }}
                     </span>
@@ -48,99 +67,147 @@
                 </div>
               </div>
 
-                <div class="flex-shrink-0 row items-right justify-end col-4">
-                <div v-if="cardState(item) === 'awaiting_code'" class="col-12 row items-center justify-end q-pb-sm">
-                  <div class="hint-text text-text text-center font9 q-mb-xs col-12">
-                    {{ $t('provider.dashboard.today_services.code_hint') }}
-                  </div>
-                  <div class="code-input-row col-12">
-                    <input
-                      v-for="(_, idx) in 4"
-                      :key="idx"
-                      :ref="el => setCodeRef(item.id, idx, el)"
-                      v-model="codeInputs[item.id][idx]"
-                      class="code-input-box font9 fontbold"
-                      type="tel"
-                      maxlength="1"
-                      inputmode="numeric"
-                      @input="onCodeInput(item, idx)"
-                      @keydown.delete="onCodeDelete(item.id, idx)"
-                      @paste.prevent="onCodePaste(item, $event)"
+              <div class="flex-shrink-0 row items-right justify-end col-4">
+                <div
+                  v-if="cardState(item) === 'awaiting_code'"
+                  class="col-12 row items-center justify-end q-pb-sm"
+                >
+
+                <div class="hint-text text-text text-center font9 q-mb-xs col-12">
+                  {{ $t('provider.dashboard.today_services.code_hint') }}
+                </div>
+
+                <div class="code-input-row col-12">
+                  <input
+                    v-for="(_, idx) in 4"
+                    :key="idx"
+                    :ref="el => setCodeRef(item.id, idx, el)"
+                    v-model="codeInputs[item.id][idx]"
+                    class="code-input-box font9 fontbold"
+                    inputmode="numeric"
+                    maxlength="1"
+                    type="tel"
+                    @input="onCodeInput(item, idx)"
+                    @keydown.delete="onCodeDelete(item.id, idx)"
+                    @paste.prevent="onCodePaste(item, $event)"
+                  />
+                </div>
+
+                <div v-if="codeError[item.id]" class="text-negative code-error-text font9 q-mt-xs text-center">
+                  {{ codeError[item.id] }}
+                </div>
+              </div>
+
+              <div
+                v-else-if="cardState(item) === 'in_progress'"
+                class="col-12 text-center q-mb-xs"
+              >
+                <div class="column items-center">
+                  <span class="text-text font9 fontmedium q-my-xs">
+                    {{ $t('provider.dashboard.today_services.in_progress') }}
+                  </span>
+ 
+                  <div class="code-pill font12 fontbold bg-positive row">
+                    <q-icon
+                      class="q-mr-xs q-my-auto"
+                      name="mdi-check"
+                      size="12px"
                     />
-                  </div>
-                  <div v-if="codeError[item.id]" class="text-negative code-error-text font9 q-mt-xs text-center">
-                    {{ codeError[item.id] }}
+
+                    {{ item.code || localVerified[item.id] }}
                   </div>
                 </div>
+              </div>
 
-                <div v-else-if="cardState(item) === 'in_progress'" class="col-12 text-center q-mb-xs">
-                  <div class="column items-center">
-                    <span class="text-text font9 fontmedium q-my-xs">
-                      {{ $t('provider.dashboard.today_services.in_progress') }}
-                    </span>
-                    <div class="code-pill font12 fontbold bg-positive row">
-                      <q-icon
-                        name="mdi-check" size="12px" class="q-mr-xs q-my-auto"
-                      />
-                      {{ item.code || localVerified[item.id] }}
-                    </div>
+              <div v-else class="">
+                <div
+                  v-if="item.provider_reviewed"
+                  class="rate-btn reviewed-badge column fontsemibold"
+                >
+                  <div class="text-center">
+                    <q-icon
+                      class="q-mr-xs"
+                      name="mdi-star"
+                      size="14px"
+                    />
                   </div>
+
+                  {{ $t('provider.dashboard.today_services.reviewed_badge') }}
                 </div>
 
                 <div v-else class="">
-                  <div v-if="item.provider_reviewed" class="rate-btn reviewed-badge column  fontsemibold">
-                    <div class="text-center">
-                      <q-icon name="mdi-star" size="14px" class="q-mr-xs " />
+                  <q-btn
+                    class="rate-btn font10 fontmedium"
+                    no-caps
+                    unelevated
+                    @click.stop="emit('rate', item)"
+                  >
+                    <div class="column items-center">
+                      <q-icon name="mdi-star-outline" size="14px" class="q-mr-xs" />
+  
+                      {{ $t('provider.dashboard.today_services.rate_btn') }}
                     </div>
-                    {{ $t('provider.dashboard.today_services.reviewed_badge') }}
-                  </div>
-                  <div v-else class="">
-                    <q-btn
-                      unelevated no-caps
-                      class="rate-btn font10 fontmedium"
-                      @click.stop="emit('rate', item)"
-                    >
-                      <div class="column items-center">
-                        <q-icon name="mdi-star-outline" size="14px" class="q-mr-xs" />
-                        {{ $t('provider.dashboard.today_services.rate_btn') }}
-                      </div>
-                    </q-btn>
-                  </div>
+                  </q-btn>
                 </div>
               </div>
             </div>
-
-            <div class="progress-track q-mb-sm">
-              <div
-                class="progress-fill"
-                :class="cardState(item) === 'finished' ? 'progress-fill--finished' : ''"
-                :style="{ width: progressByState(item) + '%' }"
+          </div>
+
+          <div class="progress-track q-mb-sm">
+            <div
+              class="progress-fill"
+              :class="cardState(item) === 'finished' ? 'progress-fill--finished' : ''"
+              :style="{ width: progressByState(item) + '%' }"
+            />
+          </div>
+
+          <div class="row items-center no-wrap">
+            <q-btn
+              class="flex-shrink-0"
+              color="primary"
+              flat no-caps dense
+              size="sm"
+              :label="$t('provider.dashboard.today_services.help')"
+              @click.stop="openHelp"
+            />
+
+            <q-space />
+
+            <template v-if="cardState(item) === 'in_progress'">
+              <q-icon
+                class="q-mr-xs flex-shrink-0 gradient-diarista"
+                name="mdi-clock-outline"
+                size="13px"
               />
-            </div>
 
-            <div class="row items-center no-wrap">
-              <q-btn
-                flat no-caps dense
-                :label="$t('provider.dashboard.today_services.help')"
-                color="primary"
-                size="sm"
-                class="flex-shrink-0"
-                @click.stop="openHelp"
-              />
-              <q-space />
-              <template v-if="cardState(item) === 'in_progress'">
-                <q-icon name="mdi-clock-outline" size="13px" class="q-mr-xs flex-shrink-0 gradient-diarista" />
-                <span class="text-grey-6 text-right text-no-wrap font11">
-                  {{ $t('provider.dashboard.today_services.end_time_label') }} 
-                  <span class="gradient-diarista">{{ calculateTimeUntilEnd(item.end_time) }}</span>
+              <span class="text-grey-6 text-right text-no-wrap font11">
+                {{ $t('provider.dashboard.today_services.end_time_label') }}
+
+                <span class="gradient-diarista">
+                  {{ calculateTimeUntilEnd(item.end_time) }}
                 </span>
-              </template>
+              </span>
+ 
+            </template>
               <template v-else-if="cardState(item) === 'awaiting_code'">
-                <q-icon name="mdi-map-marker-outline" size="13px" color="grey-6" class="q-mr-xs flex-shrink-0" />
-                <span class="text-grey-6 col ellipsis text-right font9 fontmedium">
+                <q-icon
+                  class="q-mr-xs flex-shrink-0"
+                  color="grey-6"
+                  name="mdi-map-marker-outline"
+                  size="13px"
+                />
+
+                <span class="text-grey-6 address-text ellipsis text-right font9 fontmedium">
                   {{ [item.address?.address, item.address?.number, item.address?.district].filter(Boolean).join(', ') || '—' }}
                 </span>
-                <q-icon name="mdi-content-copy" color="primary" size="16px" class="q-ml-xs" @click="copyAddress(item.address)"/>
+
+                <q-icon
+                  class="q-ml-xs"
+                  color="primary"
+                  name="mdi-content-copy"
+                  size="16px"
+                  @click="copyAddress(item.address)"
+                />
               </template>
             </div>
           </q-card-section>
@@ -151,18 +218,19 @@
 </template>
 
 <script setup>
+import { avatarColors, formatAddress, getFirstName } from 'src/helpers/utils';
 import { reactive } from 'vue'
-import { useQuasar } from 'quasar'
 import { useI18n } from 'vue-i18n'
+import { useQuasar } from 'quasar'
+import { verifyScheduleCode } from 'src/api/schedule'
+
 import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
-import { payServicePackage, verifyScheduleCode } from 'src/api/schedule'
-import { formatAddress, getFirstName } from 'src/helpers/utils';
 
 const props = defineProps({ data: { type: Array, default: () => [] } })
-const emit = defineEmits(['rate', 'refresh'])
+const emit  = defineEmits(['rate', 'refresh'])
 
-const $q = useQuasar()
 const { t } = useI18n()
+const $q    = useQuasar()
 
 const codeInputs    = reactive({})
 const codeRefs      = reactive({})
@@ -178,14 +246,21 @@ props.data.forEach(item => ensureCode(item.id))
 
 const setCodeRef = (id, idx, el) => {
   ensureCode(id)
+
   codeRefs[id][idx] = el
 }
 
+//
+
 const onCodeInput = async (item, idx) => {
   const id  = item.id
+ 
   const val = codeInputs[id][idx]
+ 
   if (!/^\d$/.test(val)) { codeInputs[id][idx] = ''; return }
+ 
   codeError[id] = null
+ 
   if (idx < 3) {
     codeRefs[id][idx + 1]?.focus()
   } else {
@@ -196,92 +271,116 @@ const onCodeInput = async (item, idx) => {
 const onCodeDelete = (id, idx) => {
   if (codeInputs[id][idx] === '' && idx > 0) {
     codeInputs[id][idx - 1] = ''
+
     codeRefs[id][idx - 1]?.focus()
   }
 }
 
 const onCodePaste = async (item, e) => {
   const text = e.clipboardData.getData('text').replace(/\D/g, '').slice(0, 4)
+
   if (text.length !== 4) return
+
   const id = item.id
+
   text.split('').forEach((ch, i) => { codeInputs[id][i] = ch })
+
   await submitCode(item)
 }
 
 const submitCode = async (item) => {
   const id   = item.id
+
   const code = codeInputs[id].join('')
+
   if (code.length < 4) return
-  try {
-    if (item.service_package_id) {
-      await payServicePackage(item.service_package_id)
-    }
 
+  try {
     const response = await verifyScheduleCode(id, code)
-    console.log(response)
+
     if (response?.payload) {
       localVerified[id] = code
       emit('refresh')
     } else {
       const msg = response?.data?.message || response?.message || t('provider.dashboard.today_services.code_error')
+
       codeError[id] = msg
       codeInputs[id] = ['', '', '', '']
+
       codeRefs[id][0]?.focus()
     }
   } catch (err) {
     const msg = err?.response?.data?.message || t('provider.dashboard.today_services.code_error')
+
     codeError[id] = msg
     codeInputs[id] = ['', '', '', '']
+
     codeRefs[id][0]?.focus()
   }
 }
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
-
-const cardState = (item) => {
-  const verified = item.code_verified || !!localVerified[item.id]
-  if (!verified) return 'awaiting_code'
-  const [h, m] = (item.end_time || '23:59').slice(0, 5).split(':').map(Number)
-  const endTime = new Date()
-  endTime.setHours(h, m, 0, 0)
-  return new Date() >= endTime ? 'finished' : 'in_progress'
-}
-
-const progressByState = (item) => {
-  const state = cardState(item)
-  if (state === 'awaiting_code') return 60
-  if (state === 'in_progress')   return 80
-  return 100
-}
+//
 
 const calculateTimeUntilEnd = (endTimeStr) => {
   const [h, m] = (endTimeStr || '23:59').slice(0, 5).split(':').map(Number)
+
   const now = new Date()
+
   const endTime = new Date()
+
   endTime.setHours(h, m, 0, 0)
+
   const diffMs = endTime - now
+
   if (diffMs <= 0) return '00:00'
+
   const diffH = Math.floor(diffMs / (1000 * 60 * 60))
+
   const diffM = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60))
+
   return `${String(diffH).padStart(2, '0')}h:${String(diffM).padStart(2, '0')}`
 }
 
-const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
+const cardState = (item) => {
+  const verified = item.code_verified || !!localVerified[item.id]
+
+  if (!verified) return 'awaiting_code'
+
+  const [h, m] = (item.end_time || '23:59').slice(0, 5).split(':').map(Number)
+
+  const endTime = new Date()
+
+  endTime.setHours(h, m, 0, 0)
+
+  return new Date() >= endTime ? 'finished' : 'in_progress'
 }
 
 const copyAddress = (address) => {
   const formatted = formatAddress(address);
+
   if (formatted) {
     navigator.clipboard.writeText(formatted);
   }
+
   $q.notify({ message: t('provider.dashboard.next_schedules.address_copied'), color: 'positive' });
 };
+
+//
+
+const progressByState = (item) => {
+  const state = cardState(item)
+
+  if (state === 'awaiting_code') return 60
+  if (state === 'in_progress')   return 80
+
+  return 100
+}
+
+//
+
+const openHelp = () => {
+  $q.dialog({ component: ProfileHelpDialog })
+}
 </script>
 
 <style scoped lang="scss">
@@ -299,10 +398,11 @@ const copyAddress = (address) => {
 }
 
 .today-card {
-  min-width: 80%;
+  min-width: 82vw;
   scroll-snap-align: start;
   border-radius: 12px;
 }
+
 .today-card--single {
   min-width: 100%;
   width: 100%;
@@ -323,7 +423,7 @@ const copyAddress = (address) => {
   padding: 4px 8px; 
   width: 110px; 
   height: 28px; 
-  margin: 0 0 0 auto; 
+  margin: 0; 
 }
 
 .code-input-box {
@@ -348,6 +448,11 @@ const copyAddress = (address) => {
   line-height: 1.2;
 }
 
+.address-text {
+  max-width: 110px;
+  min-width: 0;
+}
+
 .code-pill {
   color: white;
   letter-spacing: 2px;

+ 1 - 8
src/components/dashboard/NextSchedulesDetailsDialog.vue

@@ -191,8 +191,8 @@
 </template>
 
 <script setup>
+import { avatarColors, formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
 import { computed } from "vue";
-import { formatAddress, formatCurrency, getFirstName } from "src/helpers/utils";
 import { labelsPeriodTypes } from "src/helpers/labelsPeriodTypes.js";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
@@ -247,13 +247,6 @@ const weekdayLabel = computed(() => {
   return weekday.charAt(0).toUpperCase() + weekday.slice(1);
 });
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
-
 const copyAddress = () => {
   const address = formattedAddress.value;
 

+ 56 - 25
src/components/dashboard/ScheduleCancelDialog.vue

@@ -1,9 +1,22 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="cancel-dialog-card bg-surface shadow-card" :flat="false">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="cancel-dialog-card bg-surface shadow-card"
+      :flat="false"
+    >
       <div class="row justify-end q-pt-sm q-pr-sm">
-        <q-btn flat round dense icon="close" color="grey-6" size="sm" @click="onDialogCancel" />
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="close"
+          round
+          size="sm"
+          @click="onDialogCancel"
+        />
       </div>
 
       <q-card-section class="q-pt-none q-pb-sm q-px-lg text-center">
@@ -16,27 +29,42 @@
         <div class="text-grey-8 font14 fontbold q-mb-xs">
           {{ $t('provider.dashboard.cancel_schedule.reason_label') }}
         </div>
+
         <q-input
           v-model="cancelText"
-          type="textarea"
-          outlined
+          color="secondary"
           dense
+          hide-bottom-space
           input-class="text-dark"
-          :placeholder="$t('provider.dashboard.cancel_schedule.reason_placeholder')"
+          outlined
           rows="4"
-          color="secondary"
+          type="textarea"
+          :placeholder="$t('provider.dashboard.cancel_schedule.reason_placeholder')"
           :rules="[val => (val && val.trim().length >= 5) || ' ']"
-          hide-bottom-space
         />
       </q-card-section>
 
       <q-card-section class="q-pt-xs q-pb-md q-px-lg">
         <div class="warning-box row no-wrap q-gutter-x-sm q-pa-sm">
-          <q-icon name="mdi-alert-outline" color="secondary" size="22px" class="q-mt-xs flex-shrink-0" />
+          <q-icon
+            class="q-mt-xs flex-shrink-0"
+            color="secondary"
+            name="mdi-alert-outline"
+            size="22px"
+          />
+
           <div class="font12 text-grey-8">
-            <span>{{ $t('provider.dashboard.cancel_schedule.warning_before') }}</span>
-            <span class="fontbold">{{ $t('provider.dashboard.cancel_schedule.warning_bold') }}</span>
-            <span>{{ $t('provider.dashboard.cancel_schedule.warning_after') }}</span>
+            <span>
+              {{ $t('provider.dashboard.cancel_schedule.warning_before') }}
+            </span>
+
+            <span class="fontbold">
+              {{ $t('provider.dashboard.cancel_schedule.warning_bold') }}
+            </span>
+
+            <span>
+              {{ $t('provider.dashboard.cancel_schedule.warning_after') }}
+            </span>
           </div>
         </div>
       </q-card-section>
@@ -44,38 +72,38 @@
       <q-card-section class="q-pt-none q-pb-lg q-px-lg">
         <div class="row justify-center q-gutter-x-md">
           <q-btn
-            unelevated
-            rounded
+            class="btn-action bg-grey-3 text-grey-8"
             no-caps
+            rounded
             size="sm"
-            class="btn-action bg-grey-3 text-grey-8"
+            unelevated
             :label="$t('provider.dashboard.cancel_schedule.btn_cancel')"
             @click="onDialogCancel"
           />
+
           <q-btn
-            unelevated
-            rounded
-            no-caps
-            color="secondary"
             class="btn-action"
-            :loading="loading"
+            color="secondary"
+            no-caps
+            rounded
+            unelevated
             size="sm"
             :disable="!cancelText || cancelText.trim().length < 5"
             :label="$t('provider.dashboard.cancel_schedule.btn_keep')"
+            :loading="loading"
             @click="confirmCancel"
           />
         </div>
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { cancelSchedule } from 'src/api/schedule'
 import { useDialogPluginComponent, useQuasar } from 'quasar'
 import { useI18n } from 'vue-i18n'
-import { cancelSchedule } from 'src/api/schedule'
+import { ref } from 'vue'
 
 const props = defineProps({
   schedule: {
@@ -84,18 +112,21 @@ const props = defineProps({
   }
 })
 
+const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } = useDialogPluginComponent()
 const { t } = useI18n()
 const $q = useQuasar()
-const { dialogRef, onDialogHide, onDialogCancel, onDialogOK } = useDialogPluginComponent()
 
 const cancelText = ref('')
 const loading = ref(false)
 
 const confirmCancel = async () => {
   if (!cancelText.value || cancelText.value.trim().length < 5) return
+
   loading.value = true
+
   try {
     await cancelSchedule(props.schedule.id, cancelText.value.trim())
+
     onDialogOK()
   } catch {
     $q.notify({ message: t('http.errors.failed'), color: 'negative' })

+ 140 - 62
src/components/dashboard/ScheduleRatingDialog.vue

@@ -1,52 +1,83 @@
 <template>
-  <q-dialog ref="dialogRef" @hide="onDialogHide">
-    <q-card class="rating-dialog-card bg-surface shadow-card" :flat="false">
-
+  <q-dialog
+    ref="dialogRef"
+    @hide="onDialogHide"
+  >
+    <q-card
+      class="rating-dialog-card bg-surface shadow-card"
+      :flat="false"
+    >
       <div class="row justify-end q-pt-sm q-pr-sm">
-        <q-btn flat round dense icon="close" color="grey-6" size="sm" @click="onDialogCancel" />
+        <q-btn
+          color="grey-6"
+          dense
+          flat
+          icon="close"
+          round
+          size="sm"
+          @click="onDialogCancel"
+        />
       </div>
 
       <div class="column items-center q-pb-sm">
-        <q-avatar size="64px" class="q-mb-sm">
+        <q-avatar
+          class="q-mb-sm"
+          size="64px"
+        >
           <span
-            :style="avatarStyle"
             class="full-width full-height flex flex-center"
             style="font-size: 20px; border-radius: 50%;"
+            :style="avatarStyle"
           >
             {{ initials }}
           </span>
         </q-avatar>
-        <div class="text-text text-center font16 fontbold q-px-lg" style="line-height:1.3">
+  
+        <div
+          class="text-text text-center font16 fontbold q-px-lg"
+          style="line-height:1.3"
+        >
           {{ $t('provider.dashboard.schedule_rating.title') }}
-          <span class="text-primary">{{ (getFirstName(schedule.client_name) || '—') + '?' }}</span>
+
+          <span class="text-primary">
+            {{ (getFirstName(schedule.client_name) || '—') + '?' }}
+          </span>
         </div>
       </div>
 
       <div class="column items-center q-pb-xs">
         <q-rating
           v-model="stars"
-          :max="5"
-          size="lg"
           color="amber"
           icon="mdi-star-outline"
           icon-selected="mdi-star"
+          size="lg"
+          :max="5"
           @update:model-value="onStarsChange"
         />
       </div>
 
-      <q-card-section v-if="stars > 0" class="q-pt-xs q-pb-xs">
+      <q-card-section
+        v-if="stars > 0"
+        class="q-pt-xs q-pb-xs"
+      >
         <div class="text-grey-7 text-center font12 q-mb-sm">
           {{ isNegative ? $t('provider.dashboard.schedule_rating.negative_label') : $t('provider.dashboard.schedule_rating.positive_label') }}
         </div>
+
         <div v-if="loadingTags" class="row justify-center q-py-sm">
-          <q-spinner-dots color="primary" size="24px" />
+          <q-spinner-dots
+            color="primary"
+            size="24px"
+          />
         </div>
+
         <div v-else class="row justify-center q-gutter-xs">
           <div
             v-for="tag in tags"
             :key="tag.id"
-            class="tag-pill font12"
             :class="{ 'tag-pill--selected': selectedTagIds.includes(tag.id), 'fontsemibold': selectedTagIds.includes(tag.id) }"
+            class="tag-pill font12"
             @click="toggleTag(tag.id)"
           >
             {{ tag.description }}
@@ -58,83 +89,117 @@
         <div class="text-grey-7 font12 q-mb-xs">
           {{ $t('provider.dashboard.schedule_rating.comment_placeholder') }}
         </div>
+
         <q-input
           v-model="comment"
-          type="textarea"
-          outlined
-          dense
-          rows="3"
           color="primary"
-          input-class="text-black"
+          dense
           hide-bottom-space
+          input-class="text-black"
+          outlined
+          rows="3"
+          type="textarea"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-xs q-pb-xs q-px-lg">
         <input
           ref="photoInputRef"
-          type="file"
           accept="image/jpeg,image/png,image/webp"
-          multiple
           class="hidden"
+          multiple
+          type="file"
           @change="onPhotosSelected"
         />
+
         <div class="row q-gutter-xs">
-          <div v-for="(preview, idx) in photoPreviews" :key="idx" class="photo-thumb">
+          <div
+            v-for="(preview, idx)
+            in photoPreviews"
+            :key="idx"
+            class="photo-thumb"
+          >
             <img :src="preview" />
-            <q-btn round dense flat icon="close" size="xs" class="photo-thumb__remove" @click="removePhoto(idx)" />
+
+            <q-btn
+              class="photo-thumb__remove"
+              dense
+              flat
+              icon="close"
+              round
+              size="xs"
+              @click="removePhoto(idx)"
+            />
           </div>
-          <div v-if="photos.length < 5" class="photo-add" @click="photoInputRef.click()">
-            <q-icon name="mdi-camera-plus-outline" size="24px" color="grey-5" />
-            <div class="photo-add__label font9">{{ $t('provider.dashboard.schedule_rating.add_photo') }}</div>
+
+          <div
+            v-if="photos.length < 5"
+            class="photo-add"
+            @click="photoInputRef.click()"
+          >
+            <q-icon
+              color="grey-5"
+              name="mdi-camera-plus-outline"
+              size="24px"
+            />
+
+            <div class="photo-add__label font9">
+              {{ $t('provider.dashboard.schedule_rating.add_photo') }}
+            </div>
           </div>
         </div>
       </q-card-section>
 
-      <q-card-section v-if="isNegative" class="q-pt-xs q-pb-xs q-px-lg">
+      <q-card-section
+        v-if="isNegative"
+        class="q-pt-xs q-pb-xs q-px-lg"
+      >
         <q-checkbox
           v-model="blockClient"
-          :label="$t('provider.dashboard.schedule_rating.block_label')"
+          checked-icon="mdi-check-circle"
+          class="text-text"
           color="primary"
           keep-color
-          class="text-text"
-          checked-icon="mdi-check-circle"
           unchecked-icon="mdi-checkbox-blank-circle-outline"
+          :label="$t('provider.dashboard.schedule_rating.block_label')"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-sm q-pb-xs q-px-lg row">
         <q-btn
-          unelevated
-          rounded
-          no-caps
-          full-width
-          color="primary"
           class="submit-btn font16 fontbold col-12"
+          color="primary"
+          full-width
+          no-caps
+          rounded
+          unelevated
+          :disable="stars === 0"
           :label="$t('provider.dashboard.schedule_rating.submit_btn')"
           :loading="loading"
-          :disable="stars === 0"
           @click="submit"
         />
       </q-card-section>
 
       <q-card-section class="q-pt-xs q-pb-lg text-center">
-        <span class="text-grey-6 font12 cursor-pointer" @click="openHelp">
+        <span
+          class="text-grey-6 font12 cursor-pointer"
+          @click="openHelp"
+        >
           {{ $t('provider.dashboard.schedule_rating.help_link') }}
         </span>
       </q-card-section>
-
     </q-card>
   </q-dialog>
 </template>
 
 <script setup>
-import { ref, computed, onMounted } from 'vue'
+import { avatarColors, getFirstName } from 'src/helpers/utils'
+import { createReview, getImprovementTypes } from 'src/api/review'
+import { computed, onMounted, ref } from 'vue'
 import { useDialogPluginComponent, useQuasar } from 'quasar'
 import { useI18n } from 'vue-i18n'
-import { getFirstName } from 'src/helpers/utils'
-import { createReview, getImprovementTypes } from 'src/api/review'
 import { userStore } from 'src/stores/user'
+
 import ProfileHelpDialog from 'src/components/profile/ProfileHelpDialog.vue'
 
 const props = defineProps({
@@ -145,8 +210,10 @@ const props = defineProps({
 })
 
 defineEmits([...useDialogPluginComponent.emits])
+
 const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
 const { t } = useI18n()
+
 const $q = useQuasar()
 const store = userStore()
 
@@ -161,58 +228,59 @@ const photos = ref([])
 const photoPreviews = ref([])
 const photoInputRef = ref(null)
 
-const isNegative = computed(() => stars.value > 0 && stars.value <= 2)
-
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-]
-
 const avatarStyle = computed(() => {
   const c = avatarColors[props.schedule.client_id % avatarColors.length]
   return { background: c.background, color: c.color }
 })
 
+const isNegative = computed(() => stars.value > 0 && stars.value <= 2)
+
 const initials = computed(() =>
   getFirstName(props.schedule.client_name)?.slice(0, 2).toUpperCase() ?? '??'
 )
 
-const onStarsChange = () => {
-  selectedTagIds.value = []
-  blockClient.value = false
-}
-
-const toggleTag = (id) => {
-  const idx = selectedTagIds.value.indexOf(id)
-  if (idx === -1) selectedTagIds.value.push(id)
-  else selectedTagIds.value.splice(idx, 1)
-}
-
 const onPhotosSelected = (event) => {
   const files = Array.from(event.target.files)
+
   const remaining = 5 - photos.value.length
+
   files.slice(0, remaining).forEach(file => {
     photos.value.push(file)
     photoPreviews.value.push(URL.createObjectURL(file))
   })
+
   event.target.value = ''
 }
 
+const onStarsChange = () => {
+  selectedTagIds.value = []
+
+  blockClient.value = false
+}
+
 const removePhoto = (idx) => {
   URL.revokeObjectURL(photoPreviews.value[idx])
+
   photos.value.splice(idx, 1)
+
   photoPreviews.value.splice(idx, 1)
 }
 
-const openHelp = () => {
-  $q.dialog({ component: ProfileHelpDialog })
+const toggleTag = (id) => {
+  const idx = selectedTagIds.value.indexOf(id)
+
+  if (idx === -1) selectedTagIds.value.push(id)
+
+  else selectedTagIds.value.splice(idx, 1)
 }
 
+//
+
 const submit = async () => {
   if (stars.value === 0) return
+
   loading.value = true
+
   try {
     await createReview({
       schedule_id: props.schedule.id,
@@ -230,6 +298,7 @@ const submit = async () => {
     onDialogOK(true)
   } catch (error) {
     const status = error?.response?.status
+
     if (status === 422) {
       $q.notify({ message: t('provider.dashboard.schedule_rating.already_reviewed'), color: 'negative', icon: 'mdi-alert-circle-outline' })
     } else {
@@ -240,10 +309,19 @@ const submit = async () => {
   }
 }
 
+//
+
+const openHelp = () => {
+  $q.dialog({ component: ProfileHelpDialog })
+}
+
+//
+
 onMounted(async () => {
   loadingTags.value = true
   try {
     const result = await getImprovementTypes('provider')
+
     tags.value = result ?? []
   } catch {
     tags.value = []

+ 48 - 34
src/components/dashboard/SolicitationDetailsDialog.vue

@@ -21,7 +21,10 @@
         </div>
 
         <q-card-section class="column items-center q-pt-xs q-pb-xs">
-          <q-avatar class="q-mb-sm" size="72px">
+          <q-avatar
+            class="q-mb-sm"
+            size="72px"
+          >
             <img
               v-if="solicitation.customer_photo"
               :src="solicitation.customer_photo"
@@ -223,8 +226,13 @@
           >
             <div class="row items-center no-wrap justify-between">
               <div>
-                <span class="fontbold font12">{{ formatItemWeekday(pkgItem.date) }}</span>
-                <span class="font12">{{ ", " + formatItemDayMonth(pkgItem.date) }}</span>
+                <span class="fontbold font12">
+                  {{ formatItemWeekday(pkgItem.date) }}
+                </span>
+
+                <span class="font12">
+                  {{ ", " + formatItemDayMonth(pkgItem.date) }}
+                </span>
               </div>
 
               <span class="fontbold font12">
@@ -265,8 +273,8 @@
               size="20px"
             />
 
-            <span class="font16 fontbold no-wrap text-center">
-              {{ solicitation.address?.district || "N/A" }}
+            <span class="font16 fontbold text-center">
+              {{ formatAddress(solicitation.address) || "N/A" }}
             </span>
           </div>
 
@@ -391,8 +399,8 @@
 </template>
 
 <script setup>
+import { avatarColors, formatAddress, formatCurrency } from "src/helpers/utils";
 import { computed, ref } from "vue";
-import { formatCurrency } from "src/helpers/utils";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 import { useI18n } from "vue-i18n";
 
@@ -450,6 +458,14 @@ const paidTotalAmount = computed(() =>
   ),
 );
 
+const packageCountLabel = computed(() =>
+  packageItems.value.length > 1
+    ? t("provider.dashboard.solicitations.package_count", {
+        count: packageItems.value.length,
+      })
+    : "",
+);
+
 const packageItems = computed(() => {
   const items = props.solicitation?.package_items;
 
@@ -460,14 +476,6 @@ const packageItems = computed(() => {
   return [props.solicitation];
 });
 
-const packageCountLabel = computed(() =>
-  packageItems.value.length > 1
-    ? t("provider.dashboard.solicitations.package_count", {
-        count: packageItems.value.length,
-      })
-    : "",
-);
-
 const packageTotal = computed(() =>
   packageItems.value.reduce(
     (total, item) => total + parseAmount(item.total_amount),
@@ -486,6 +494,7 @@ const serviceFee = computed(() => {
 
 const servicesList = computed(() => {
   const services = [];
+
   const solicitation = props.solicitation;
 
   if (
@@ -516,11 +525,12 @@ const servicesList = computed(() => {
   return services;
 });
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
+
+const WEEKDAYS = ["Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"];
+
+const MONTHS = [
+  "janeiro", "fevereiro", "março", "abril", "maio", "junho",
+  "julho", "agosto", "setembro", "outubro", "novembro", "dezembro",
 ];
 
 const backFromConfirm = () => {
@@ -553,25 +563,27 @@ const confirmReject = () => {
   });
 };
 
-const WEEKDAYS = ["Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"];
-
-const MONTHS = [
-  "janeiro", "fevereiro", "março", "abril", "maio", "junho",
-  "julho", "agosto", "setembro", "outubro", "novembro", "dezembro",
-];
+//
 
 const formatItemWeekday = (iso) => {
   const date = parseLocalDate(iso);
+
   if (!date) return "";
+
   return WEEKDAYS[date.getDay()];
 };
 
 const formatItemDayMonth = (iso) => {
   const date = parseLocalDate(iso);
+
   if (!date) return "";
+
   const day = String(date.getDate()).padStart(2, "0");
+
   const month = MONTHS[date.getMonth()];
+
   const year = date.getFullYear();
+
   return `${day} de ${month} de ${year}`;
 };
 
@@ -584,15 +596,6 @@ const formatAddressShort = (address) => {
   return street + district;
 };
 
-const openCancelDialog = () => {
-  $q.dialog({
-    component: ScheduleCancelDialog,
-    componentProps: { schedule: props.solicitation },
-  }).onOk(() => {
-    onDialogOK({ action: "cancelled", id: props.solicitation.id });
-  });
-};
-
 const parseLocalDate = (dateString) => {
   if (!dateString) return null;
 
@@ -613,6 +616,17 @@ const parseLocalDate = (dateString) => {
   return null;
 };
 
+//
+
+const openCancelDialog = () => {
+  $q.dialog({
+    component: ScheduleCancelDialog,
+    componentProps: { schedule: props.solicitation },
+  }).onOk(() => {
+    onDialogOK({ action: "cancelled", id: props.solicitation.id });
+  });
+};
+
 const parseAmount = (value) => {
   const amount = Number.parseFloat(value);
 

+ 2 - 7
src/components/profile/ProfilePrivacyDialog.vue

@@ -82,6 +82,7 @@
 <script setup>
 import { ref, onMounted } from 'vue'
 import { useDialogPluginComponent } from 'quasar'
+import { avatarColors } from 'src/helpers/utils'
 import { userStore } from 'src/stores/user'
 import { getProviderClientBlocks, deleteProviderClientBlock } from 'src/api/providerClientBlock'
 import diarinho from 'src/assets/diarinho_perfil_cliente_favoritos.svg'
@@ -94,13 +95,7 @@ const blocks = ref([])
 const loading = ref(false)
 const unblockingId = ref(null)
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-  { background: '#ede0ff', color: '#6200ea' },
-]
+
 
 const avatarStyle = (block) => {
   const idx = (block?.client_id ?? 0) % avatarColors.length

+ 8 - 0
src/helpers/utils.js

@@ -267,7 +267,15 @@ const getFirstName = (fullName) => {
     .split(/\s+/)[0];
 };
 
+const avatarColors = [
+  { background: "#ffd5df", color: "#932e57" },
+  { background: "#d7e8ff", color: "#2158a8" },
+  { background: "#dfd",    color: "#2a7a3b" },
+  { background: "#ffe5cc", color: "#8a4500" },
+];
+
 export {
+  avatarColors,
   formatDateDMYtoYMD,
   formatDateYMDtoDMY,
   excerpt,

+ 2 - 7
src/pages/calendar/CalendarPage.vue

@@ -253,7 +253,7 @@
 
 
 <script setup>
-import { formatCurrency, getFirstName } from 'src/helpers/utils';
+import { avatarColors, formatCurrency, getFirstName } from 'src/helpers/utils';
 import { getProviderCalendar } from 'src/api/providerCalendar';
 import { labelsPeriodTypes } from 'src/helpers/labelsPeriodTypes.js';
 import { onMounted, ref } from 'vue';
@@ -266,12 +266,7 @@ import ScheduleRatingDialog from 'src/components/dashboard/ScheduleRatingDialog.
 const $q = useQuasar();
 const { t } = useI18n();
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' },
-];
+
 
 const loading = ref(true);
 const upcomingSchedules = ref([]);

+ 2 - 6
src/pages/opportunities/OpportunitiesPage.vue

@@ -156,6 +156,7 @@
 
 <script setup>
 import {
+  avatarColors,
   chooseprice,
   filterCurrency,
   getFirstName,
@@ -180,12 +181,7 @@ const { t } = useI18n();
 const opportunities = ref([]);
 const loading = ref(false);
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
+
 
 const goToOpportunityDetails = (item) => {
   const id = item.custom_schedule?.id || item.id;

+ 2 - 6
src/pages/opportunities/components/OpportunityDetailsDialog.vue

@@ -237,6 +237,7 @@ import {
   proposalOpportunity
 } from 'src/api/opportunities'
 
+import { avatarColors } from 'src/helpers/utils'
 import { labelsPeriodTypes } from 'src/helpers/labelsPeriodTypes.js'
 import { onMounted, ref } from 'vue'
 import { useI18n } from 'vue-i18n'
@@ -273,12 +274,7 @@ const user = userStore()
 const details = ref(null)
 const loading = ref(true)
 
-const avatarColors = [
-  { background: '#ffd5df', color: '#932e57' },
-  { background: '#d7e8ff', color: '#2158a8' },
-  { background: '#dfd',    color: '#2a7a3b' },
-  { background: '#ffe5cc', color: '#8a4500' }
-]
+
 
 const choosePrice = (periodType) => {
   switch (periodType) {

+ 2 - 7
src/pages/payments/PaymentsPage.vue

@@ -283,7 +283,7 @@
 
 <script setup>
 import { computed, onMounted, ref } from "vue";
-import { formatCurrency } from "src/helpers/utils";
+import { avatarColors, formatCurrency } from "src/helpers/utils";
 
 import {
   getBalance,
@@ -346,12 +346,7 @@ const canWithdraw = computed(
     !withdrawLoading.value,
 );
 
-const avatarColors = [
-  { background: "#ffd5df", color: "#932e57" },
-  { background: "#d7e8ff", color: "#2158a8" },
-  { background: "#dfd",    color: "#2a7a3b" },
-  { background: "#ffe5cc", color: "#8a4500" },
-];
+
 
 const periods = [
   {