Browse Source

feat: corrige holiday

ebagabee 2 weeks ago
parent
commit
1d74846d01
1 changed files with 290 additions and 253 deletions
  1. 290 253
      src/pages/dashboard/components/FeriadosDialog.vue

+ 290 - 253
src/pages/dashboard/components/FeriadosDialog.vue

@@ -1,164 +1,201 @@
 <template>
   <q-dialog ref="dialogRef" @hide="onDialogHide">
     <q-card class="q-dialog-plugin feriados-dialog">
-      <DefaultDialogHeader title="Feriados" @close="onDialogCancel" />
-
-      <q-card-section class="row q-col-gutter-md q-pt-none">
-        <!-- Calendário -->
-        <div class="col-12 col-sm-7">
-          <!-- Navegação mês/ano -->
-          <div class="row items-center justify-between q-mb-sm">
-            <q-btn
-              flat
-              round
-              dense
-              icon="mdi-chevron-left"
-              color="grey-7"
-              @click="prevMonth"
-            />
-            <div class="row q-gutter-sm items-center">
-              <q-select
-                v-model="currentMonth"
-                :options="monthOptions"
-                emit-value
-                map-options
+      <template v-if="view === 'calendar'">
+        <DefaultDialogHeader title="Feriados" @close="onClose" />
+
+        <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
+                flat
+                round
                 dense
-                outlined
-                style="min-width: 100px"
+                icon="mdi-chevron-left"
+                color="grey-7"
+                @click="prevMonth"
               />
-              <q-select
-                v-model="currentYear"
-                :options="yearOptions"
-                emit-value
-                map-options
+              <div class="row items-center q-gutter-xs">
+                <q-select
+                  v-model="currentMonth"
+                  :options="monthOptions"
+                  emit-value
+                  map-options
+                  dense
+                  outlined
+                  style="min-width: 100px"
+                />
+                <q-select
+                  v-model="currentYear"
+                  :options="yearOptions"
+                  emit-value
+                  map-options
+                  dense
+                  outlined
+                  style="min-width: 80px"
+                />
+              </div>
+              <q-btn
+                flat
+                round
                 dense
-                outlined
-                style="min-width: 80px"
+                icon="mdi-chevron-right"
+                color="grey-7"
+                @click="nextMonth"
               />
             </div>
-            <q-btn
-              flat
-              round
-              dense
-              icon="mdi-chevron-right"
-              color="grey-7"
-              @click="nextMonth"
-            />
-          </div>
 
-          <!-- Cabeçalho dias da semana -->
-          <div class="calendar-grid q-mb-xs">
-            <div
-              v-for="day in weekDays"
-              :key="day"
-              class="calendar-header-cell text-caption text-grey-6 text-center"
-            >
-              {{ day }}
+            <div class="calendar-grid q-mb-xs">
+              <div
+                v-for="day in weekDays"
+                :key="day"
+                class="cal-header text-caption text-grey-6 text-center text-weight-medium"
+              >
+                {{ day }}
+              </div>
             </div>
-          </div>
 
-          <!-- Dias do mês -->
-          <div class="calendar-grid">
-            <div
-              v-for="(cell, index) in calendarCells"
-              :key="index"
-              class="calendar-cell"
-              :class="{
-                'calendar-cell--empty': !cell.day,
-                'calendar-cell--holiday': cell.isHoliday,
-                'calendar-cell--selected': cell.day === selectedDay && cell.day !== null,
-              }"
-              @click="cell.day && onDayClick(cell)"
-            >
-              <span v-if="cell.day">{{ cell.day }}</span>
+            <div class="calendar-grid">
+              <div
+                v-for="(cell, index) in calendarCells"
+                :key="index"
+                class="cal-cell"
+                :class="{
+                  'cal-cell--empty': !cell.day,
+                  'cal-cell--holiday': cell.isHoliday,
+                }"
+                @click="cell.day && !cell.isHoliday && openNewRecord(cell.day)"
+              >
+                <span v-if="cell.day" class="cal-cell__number">{{
+                  cell.day
+                }}</span>
+              </div>
             </div>
           </div>
 
-          <!-- Input inline para adicionar feriado -->
-          <div v-if="selectedDay !== null && !selectedDayHasHoliday" class="q-mt-md">
-            <div class="text-caption text-grey-6 q-mb-xs">
-              Descrição para {{ selectedDay }}/{{ String(currentMonth).padStart(2, '0') }}/{{ currentYear }}
+          <div class="col-12 col-sm-5">
+            <div class="text-subtitle2 q-mb-sm">Resumo</div>
+            <q-separator class="q-mb-sm" />
+
+            <div v-if="loadingHolidays" class="flex flex-center q-py-lg">
+              <q-spinner color="primary" size="24px" />
             </div>
-            <div class="row q-gutter-sm">
-              <q-input
-                v-model="newDescription"
-                dense
-                outlined
-                placeholder="Ex: Independência do Brasil"
-                class="col"
-                autofocus
-                @keyup.enter="addHoliday"
-              />
-              <q-btn
-                unelevated
-                color="primary"
-                label="Adicionar"
-                no-caps
-                :disable="!newDescription.trim()"
-                @click="addHoliday"
-              />
+
+            <div
+              v-else-if="monthHolidays.length === 0"
+              class="text-caption text-grey-5 text-center q-mt-lg q-px-sm"
+            >
+              Clique em um dia no calendário para adicionar um registro.
             </div>
-          </div>
-        </div>
 
-        <!-- Resumo -->
-        <div class="col-12 col-sm-5">
-          <div class="text-subtitle2 q-mb-sm">Resumo</div>
-          <q-separator class="q-mb-sm" />
+            <q-list v-else separator>
+              <q-item
+                v-for="holiday in monthHolidays"
+                :key="holiday.id"
+                class="q-pa-sm"
+              >
+                <q-item-section avatar>
+                  <q-avatar
+                    size="36px"
+                    color="deep-orange"
+                    text-color="white"
+                    class="text-weight-bold"
+                  >
+                    {{ holiday.day }}
+                  </q-avatar>
+                </q-item-section>
+                <q-item-section>
+                  <q-item-label class="text-body2">{{
+                    holiday.description
+                  }}</q-item-label>
+                  <q-item-label caption>{{
+                    typeLabel(holiday.type)
+                  }}</q-item-label>
+                </q-item-section>
+                <q-item-section side>
+                  <q-btn
+                    flat
+                    round
+                    dense
+                    icon="mdi-trash-can-outline"
+                    color="grey-5"
+                    size="sm"
+                    :loading="deletingId === holiday.id"
+                    @click="removeHoliday(holiday)"
+                  />
+                </q-item-section>
+              </q-item>
+            </q-list>
 
-          <div v-if="monthHolidays.length === 0" class="text-caption text-grey-5 text-center q-mt-lg">
-            Clique em um dia no calendário para adicionar um registro.
-          </div>
-
-          <q-list v-else separator>
-            <q-item
-              v-for="holiday in monthHolidays"
-              :key="holiday._key"
-              class="q-pa-sm"
+            <div
+              v-if="monthHolidays.length > 0"
+              class="text-caption text-grey-5 text-center q-mt-md q-px-sm"
             >
-              <q-item-section avatar>
-                <q-avatar
-                  size="36px"
-                  color="deep-orange"
-                  text-color="white"
-                  class="text-weight-bold"
-                >
-                  {{ holiday.day }}
-                </q-avatar>
-              </q-item-section>
-              <q-item-section>
-                <q-item-label class="text-body2">{{ holiday.description }}</q-item-label>
-                <q-item-label caption>Feriado</q-item-label>
-              </q-item-section>
-              <q-item-section side>
-                <q-btn
-                  flat
-                  round
-                  dense
-                  icon="mdi-trash-can-outline"
-                  color="grey-5"
-                  size="sm"
-                  @click="removeHoliday(holiday)"
-                />
-              </q-item-section>
-            </q-item>
-          </q-list>
-        </div>
-      </q-card-section>
-
-      <q-separator />
-
-      <q-card-actions align="right">
-        <q-btn outline color="primary" label="CANCELAR" no-caps @click="onDialogCancel" />
-        <q-btn
-          unelevated
-          color="primary"
-          label="SALVAR"
-          no-caps
-          :loading="saving"
-          @click="onSave"
+              Clique em um dia no calendário para adicionar um registro.
+            </div>
+          </div>
+        </q-card-section>
+
+        <q-separator />
+
+        <q-card-actions align="right">
+          <q-btn
+            outline
+            color="primary"
+            label="FECHAR"
+            no-caps
+            @click="onClose"
+          />
+        </q-card-actions>
+      </template>
+
+      <template v-else>
+        <DefaultDialogHeader
+          :title="`Novo Registro: ${formattedSelectedDate}`"
+          @close="cancelNewRecord"
         />
-      </q-card-actions>
+
+        <q-card-section class="column q-gutter-md q-pt-sm">
+          <DefaultInput
+            v-model="newDescription"
+            label="Nome do Evento"
+            placeholder="Ex: Ponto facultativo, Natal..."
+            icon="mdi-pencil-outline"
+            outlined
+            autofocus
+          />
+
+          <DefaultSelect
+            v-model="newType"
+            label="Selecione o Tipo."
+            :options="typeOptions"
+            emit-value
+            map-options
+            outlined
+          />
+        </q-card-section>
+
+        <q-separator />
+
+        <q-card-actions align="right">
+          <q-btn
+            outline
+            color="primary"
+            label="CANCELAR"
+            no-caps
+            @click="cancelNewRecord"
+          />
+          <q-btn
+            unelevated
+            color="primary"
+            label="SALVAR"
+            no-caps
+            :loading="saving"
+            :disable="!newDescription.trim()"
+            @click="saveNewRecord"
+          />
+        </q-card-actions>
+      </template>
     </q-card>
   </q-dialog>
 </template>
@@ -167,26 +204,28 @@
 import { ref, computed, onMounted } from "vue";
 import { useDialogPluginComponent, useQuasar } from "quasar";
 import DefaultDialogHeader from "src/components/defaults/DefaultDialogHeader.vue";
+import DefaultInput from "src/components/defaults/DefaultInput.vue";
+import DefaultSelect from "src/components/defaults/DefaultSelect.vue";
 import { getHolidays, createHoliday, deleteHoliday } from "src/api/holiday";
 
 defineEmits([...useDialogPluginComponent.emits]);
 
-const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent();
+const { dialogRef, onDialogHide, onDialogOK } = useDialogPluginComponent();
 const $q = useQuasar();
 
 const now = new Date();
-const currentMonth = ref(now.getMonth() + 1); // 1-12
+const currentMonth = ref(now.getMonth() + 1);
 const currentYear = ref(now.getFullYear());
+
+const view = ref("calendar");
 const selectedDay = ref(null);
 const newDescription = ref("");
-const saving = ref(false);
+const newType = ref("feriado");
 
-// Holidays loaded from API
-const existingHolidays = ref([]); // { id, holiday_date, description }
-// Pending additions (not yet saved)
-const pendingAdditions = ref([]); // { _key, holiday_date, description, day }
-// IDs to delete on save
-const pendingDeletions = ref([]); // ids
+const existingHolidays = ref([]);
+const loadingHolidays = ref(false);
+const saving = ref(false);
+const deletingId = ref(null);
 
 const weekDays = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
 
@@ -205,6 +244,11 @@ const monthOptions = [
   { label: "Dec", value: 12 },
 ];
 
+const typeOptions = [
+  { label: "Feriado", value: "feriado" },
+  { label: "Ponto Facultativo", value: "facultativo" },
+];
+
 const yearOptions = computed(() => {
   const base = now.getFullYear();
   return Array.from({ length: 7 }, (_, i) => {
@@ -213,58 +257,48 @@ const yearOptions = computed(() => {
   });
 });
 
-// All holidays for current view (existing - deleted + pending)
-const allHolidays = computed(() => {
-  const active = existingHolidays.value
-    .filter((h) => !pendingDeletions.value.includes(h.id))
+const monthHolidays = computed(() =>
+  existingHolidays.value
+    .filter((h) => {
+      const d = new Date(h.holiday_date + "T00:00:00");
+      return (
+        d.getMonth() + 1 === currentMonth.value &&
+        d.getFullYear() === currentYear.value
+      );
+    })
     .map((h) => {
       const d = new Date(h.holiday_date + "T00:00:00");
-      return {
-        _key: `existing-${h.id}`,
-        id: h.id,
-        isPending: false,
-        holiday_date: h.holiday_date,
-        day: d.getDate(),
-        month: d.getMonth() + 1,
-        year: d.getFullYear(),
-        description: h.description,
-      };
-    });
-  const additions = pendingAdditions.value.map((h) => ({ ...h, isPending: true }));
-  return [...active, ...additions];
-});
-
-// Holidays for selected month
-const monthHolidays = computed(() => {
-  return allHolidays.value
-    .filter((h) => h.month === currentMonth.value && h.year === currentYear.value)
-    .sort((a, b) => a.day - b.day);
-});
+      return { ...h, day: d.getDate() };
+    })
+    .sort((a, b) => a.day - b.day),
+);
 
-// Compute calendar cells
 const calendarCells = computed(() => {
   const year = currentYear.value;
   const month = currentMonth.value;
-  const firstDay = new Date(year, month - 1, 1).getDay(); // 0=Sun
+  const firstDay = new Date(year, month - 1, 1).getDay();
   const daysInMonth = new Date(year, month, 0).getDate();
-
   const holidayDays = new Set(monthHolidays.value.map((h) => h.day));
 
   const cells = [];
-  for (let i = 0; i < firstDay; i++) {
+  for (let i = 0; i < firstDay; i++)
     cells.push({ day: null, isHoliday: false });
-  }
-  for (let d = 1; d <= daysInMonth; d++) {
+  for (let d = 1; d <= daysInMonth; d++)
     cells.push({ day: d, isHoliday: holidayDays.has(d) });
-  }
   return cells;
 });
 
-const selectedDayHasHoliday = computed(() => {
-  if (selectedDay.value === null) return false;
-  return monthHolidays.value.some((h) => h.day === selectedDay.value);
+const formattedSelectedDate = computed(() => {
+  if (!selectedDay.value) return "";
+  const dd = String(selectedDay.value).padStart(2, "0");
+  const mm = String(currentMonth.value).padStart(2, "0");
+  return `${dd}/${mm}/${currentYear.value}`;
 });
 
+function typeLabel(type) {
+  return type === "facultativo" ? "Ponto Facultativo" : "Feriado";
+}
+
 function prevMonth() {
   if (currentMonth.value === 1) {
     currentMonth.value = 12;
@@ -272,8 +306,6 @@ function prevMonth() {
   } else {
     currentMonth.value -= 1;
   }
-  selectedDay.value = null;
-  newDescription.value = "";
 }
 
 function nextMonth() {
@@ -283,22 +315,21 @@ function nextMonth() {
   } else {
     currentMonth.value += 1;
   }
-  selectedDay.value = null;
-  newDescription.value = "";
 }
 
-function onDayClick(cell) {
-  if (selectedDayHasHoliday.value && selectedDay.value === cell.day) {
-    // Toggle off
-    selectedDay.value = null;
-    newDescription.value = "";
-    return;
-  }
-  selectedDay.value = cell.day;
+function openNewRecord(day) {
+  selectedDay.value = day;
   newDescription.value = "";
+  newType.value = "feriado";
+  view.value = "new-record";
+}
+
+function cancelNewRecord() {
+  view.value = "calendar";
+  selectedDay.value = null;
 }
 
-function addHoliday() {
+async function saveNewRecord() {
   const desc = newDescription.value.trim();
   if (!desc || selectedDay.value === null) return;
 
@@ -306,59 +337,59 @@ function addHoliday() {
   const dd = String(selectedDay.value).padStart(2, "0");
   const dateStr = `${currentYear.value}-${mm}-${dd}`;
 
-  pendingAdditions.value.push({
-    _key: `pending-${Date.now()}`,
-    id: null,
-    isPending: true,
-    holiday_date: dateStr,
-    day: selectedDay.value,
-    month: currentMonth.value,
-    year: currentYear.value,
-    description: desc,
-  });
-
-  selectedDay.value = null;
-  newDescription.value = "";
-}
-
-function removeHoliday(holiday) {
-  if (holiday.isPending) {
-    pendingAdditions.value = pendingAdditions.value.filter((h) => h._key !== holiday._key);
-  } else {
-    pendingDeletions.value.push(holiday.id);
-  }
-  if (selectedDay.value === holiday.day) {
+  saving.value = true;
+  try {
+    await createHoliday({
+      holiday_date: dateStr,
+      description: desc,
+      type: newType.value,
+    });
+    await loadHolidays();
+    view.value = "calendar";
     selectedDay.value = null;
-    newDescription.value = "";
+  } catch {
+    $q.notify({
+      type: "negative",
+      message: "Erro ao salvar feriado. Tente novamente.",
+    });
+  } finally {
+    saving.value = false;
   }
 }
 
-async function onSave() {
-  saving.value = true;
+async function removeHoliday(holiday) {
+  deletingId.value = holiday.id;
   try {
-    // Delete first
-    for (const id of pendingDeletions.value) {
-      await deleteHoliday(id);
-    }
-    // Then create
-    for (const h of pendingAdditions.value) {
-      await createHoliday({ holiday_date: h.holiday_date, description: h.description });
-    }
-    onDialogOK(true);
+    await deleteHoliday(holiday.id);
+    existingHolidays.value = existingHolidays.value.filter(
+      (h) => h.id !== holiday.id,
+    );
   } catch {
-    $q.notify({ type: "negative", message: "Erro ao salvar feriados. Tente novamente." });
+    $q.notify({
+      type: "negative",
+      message: "Erro ao remover feriado. Tente novamente.",
+    });
   } finally {
-    saving.value = false;
+    deletingId.value = null;
   }
 }
 
-onMounted(async () => {
+async function loadHolidays() {
+  loadingHolidays.value = true;
   try {
     existingHolidays.value = await getHolidays();
   } catch {
     $q.notify({ type: "negative", message: "Erro ao carregar feriados." });
+  } finally {
+    loadingHolidays.value = false;
   }
-});
+}
+
+function onClose() {
+  onDialogOK(true);
+}
+
+onMounted(loadHolidays);
 </script>
 
 <style scoped>
@@ -370,47 +401,53 @@ onMounted(async () => {
 .calendar-grid {
   display: grid;
   grid-template-columns: repeat(7, 1fr);
-  gap: 4px;
+  gap: 2px;
 }
 
-.calendar-header-cell {
-  padding: 4px 0;
+.cal-header {
+  padding: 6px 0 4px;
   font-weight: 600;
+  text-align: center;
 }
 
-.calendar-cell {
+.cal-cell {
   aspect-ratio: 1;
   display: flex;
   align-items: center;
   justify-content: center;
-  border-radius: 8px;
+  border-radius: 50%;
   cursor: pointer;
-  font-size: 14px;
   transition: background 0.15s;
 }
 
-.calendar-cell:hover:not(.calendar-cell--empty) {
+.cal-cell:hover:not(.cal-cell--empty):not(.cal-cell--holiday) {
   background: #f5f5f5;
 }
 
-.calendar-cell--empty {
+.cal-cell--empty {
   cursor: default;
   pointer-events: none;
 }
 
-.calendar-cell--holiday {
+.cal-cell__number {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  border-radius: 50%;
+  font-size: 13px;
+  font-weight: 500;
+  line-height: 1;
+}
+
+.cal-cell--holiday .cal-cell__number {
   background: #e64a19;
   color: #fff;
   font-weight: 700;
 }
 
-.calendar-cell--holiday:hover {
-  background: #bf360c !important;
-}
-
-.calendar-cell--selected:not(.calendar-cell--holiday) {
-  background: #ffe0d6;
-  color: #e64a19;
-  font-weight: 700;
+.cal-cell--holiday {
+  cursor: default;
 }
 </style>