Преглед изворни кода

feat(class): página da agenda integrada à API

Toolbar com navegação centralizada e botão Nova Atividade, consumo
do endpoint /class e abertura dos diálogos de presença/criação.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ebagabee пре 1 месец
родитељ
комит
65d6efb4d2
1 измењених фајлова са 186 додато и 0 уклоњено
  1. 186 0
      src/pages/classes/ClassPage.vue

+ 186 - 0
src/pages/classes/ClassPage.vue

@@ -1,9 +1,195 @@
 <template>
   <div>
     <DefaultHeaderPage title="Agenda" />
+
+    <div class="q-px-md q-pb-md">
+      <!-- Toolbar: Hoje | ‹ data › centralizada | + Nova Atividade -->
+      <div class="class-toolbar q-mb-md">
+        <!-- Esquerda -->
+        <div class="row items-center no-wrap" style="gap: 8px">
+          <q-btn
+            outline
+            color="primary"
+            label="Hoje"
+            no-caps
+            dense
+            class="q-px-md"
+            @click="goToday"
+          />
+          <q-btn
+            v-if="view === 'day'"
+            flat
+            no-caps
+            dense
+            color="primary"
+            icon="mdi-calendar-month-outline"
+            label="Voltar ao calendário"
+            class="q-px-sm"
+            @click="backToMonth"
+          />
+        </div>
+
+        <!-- Centro: ‹ Junho de 2026 › -->
+        <div class="row items-center no-wrap justify-center" style="gap: 4px">
+          <q-btn flat round dense icon="mdi-chevron-left" color="primary" @click="goPrev" />
+          <span class="text-h6 text-primary text-weight-medium" style="min-width: 220px; text-align: center">
+            {{ headerLabel }}
+          </span>
+          <q-btn flat round dense icon="mdi-chevron-right" color="primary" @click="goNext" />
+        </div>
+
+        <!-- Direita -->
+        <div class="row items-center no-wrap justify-end">
+          <q-btn
+            color="primary"
+            icon="mdi-plus"
+            label="Nova Atividade"
+            no-caps
+            unelevated
+            @click="onAddClick"
+          />
+        </div>
+      </div>
+
+      <div v-if="loading" class="flex flex-center q-pa-xl">
+        <q-spinner color="primary" size="48px" />
+      </div>
+
+      <template v-else>
+        <MonthGrid
+          v-if="view === 'month'"
+          :current-date="currentDate"
+          :classes="classes"
+          @day-click="openDay"
+          @class-click="onClassClick"
+        />
+
+        <DayTimeline
+          v-else
+          :date="currentDate"
+          :classes="classes"
+          @class-click="onClassClick"
+        />
+      </template>
+    </div>
   </div>
 </template>
 
 <script setup>
+import { ref, computed, onMounted, watch, defineAsyncComponent } from "vue";
+import { useQuasar } from "quasar";
+import {
+  addMonths,
+  subMonths,
+  addDays,
+  subDays,
+  format,
+} from "date-fns";
+import { ptBR } from "date-fns/locale";
+
 import DefaultHeaderPage from "src/components/layout/DefaultHeaderPage.vue";
+import MonthGrid from "./components/MonthGrid.vue";
+import DayTimeline from "./components/DayTimeline.vue";
+import { getClasses } from "src/api/class";
+import { userStore } from "src/stores/user";
+
+const ClassAttendanceDialog = defineAsyncComponent(
+  () => import("./components/ClassAttendanceDialog.vue"),
+);
+const AddEditClassDialog = defineAsyncComponent(
+  () => import("./components/AddEditClassDialog.vue"),
+);
+
+const $q = useQuasar();
+const { selectedUnit } = userStore();
+
+const view = ref("month"); // 'month' | 'day'
+const currentDate = ref(new Date());
+const classes = ref([]);
+const loading = ref(false);
+
+const headerLabel = computed(() => {
+  if (view.value === "month") {
+    const label = format(currentDate.value, "MMMM 'de' yyyy", { locale: ptBR });
+    return label.charAt(0).toUpperCase() + label.slice(1);
+  }
+  const label = format(currentDate.value, "EEEE, d 'de' MMMM", { locale: ptBR });
+  return label.charAt(0).toUpperCase() + label.slice(1);
+});
+
+const loadClasses = async () => {
+  loading.value = true;
+  try {
+    classes.value = await getClasses();
+  } finally {
+    loading.value = false;
+  }
+};
+
+const goPrev = () => {
+  currentDate.value =
+    view.value === "month"
+      ? subMonths(currentDate.value, 1)
+      : subDays(currentDate.value, 1);
+};
+
+const goNext = () => {
+  currentDate.value =
+    view.value === "month"
+      ? addMonths(currentDate.value, 1)
+      : addDays(currentDate.value, 1);
+};
+
+const goToday = () => {
+  currentDate.value = new Date();
+};
+
+const openDay = (date) => {
+  currentDate.value = date;
+  view.value = "day";
+};
+
+const backToMonth = () => {
+  view.value = "month";
+};
+
+// Clicar numa aula abre o dialog de registrar presença
+const onClassClick = (item) => {
+  $q.dialog({
+    component: ClassAttendanceDialog,
+    componentProps: { classData: item },
+  }).onOk(() => loadClasses());
+};
+
+// Adicionar nova atividade (opcionalmente já com a data do dia clicado)
+const openCreate = (date = null) => {
+  $q.dialog({
+    component: AddEditClassDialog,
+    componentProps: {
+      initialDate: date ? format(date, "yyyy-MM-dd") : null,
+    },
+  }).onOk(() => loadClasses());
+};
+
+// Na visão de dia já pré-seleciona a data atual
+const onAddClick = () => openCreate(view.value === "day" ? currentDate.value : null);
+
+// Recarrega quando o usuário troca de unidade no seletor do topo
+watch(
+  () => selectedUnit?.id,
+  (newId, oldId) => {
+    if (newId !== oldId) loadClasses();
+  },
+);
+
+onMounted(loadClasses);
 </script>
+
+<style scoped>
+.class-toolbar {
+  display: grid;
+  grid-template-columns: 1fr auto 1fr;
+  align-items: center;
+  gap: 8px;
+}
+</style>