Introdução

O componente Calendário é responsável pela visualização centralizada das avaliações de todas as disciplinas de um semestre. Este componente foi desenhado para oferecer uma visão global, filtrável e interativa das avaliações, integrando dados dinâmicos provenientes de ficheiros ucs.json e mantendo consistência visual com o resto da interface do utilizador.

Resumo das funcionalidades principais
  • Visualização mensal de avaliações com navegação entre meses.
  • Vista alternativa em lista cronológica de todas as avaliações.
  • Vista heatmap semestral, representando intensidade de carga académica.
  • Filtros por disciplina com seleção múltipla e legenda de cores.
  • Integração direta com dados JSON de unidades curriculares.
  • Comportamento responsivo e acessível, com foco em desempenho.

Instalação

1

Instalar dependências

Certifica-te de que tens instalado o pacote de componentes base (Shadcn Vue ou equivalente) e as dependências utilizadas pelo calendário.
npm install date-fns tailwindcss lucide-vue-next
2

Importar e registar o componente

Importa o componente no contexto onde será utilizado (exemplo: página de avaliações ou dashboard académico).
import CalendarComponent from "@/components/Calendar.vue";
Compatibilidade de versões

O componente foi desenvolvido para Vue 3 com suporte a Composition API. Não é compatível com Vue 2 sem alterações. Certifica-te de que o teu projeto utiliza vue@^3.3+ e vite ou nuxt 3.

Utilização Base

O exemplo seguinte demonstra a utilização mínima do componente de calendário. Por omissão, este apresenta-se em modo de visualização calendar (mensal), carregando internamente os dados das disciplinas e avaliações.

Exemplo básico
<template>
  <div class="p-6">
    <Calendar />
  </div>
</template>

<script setup lang="ts">
import Calendar from '@/components/ui/calendar'
</script>
Reatividade

O componente não requer propriedades obrigatórias. Caso não sejam fornecidos dados externos, ele inicializa-se com o estado interno e os valores padrão definidos na sua configuração. É, no entanto, possível passar explicitamente listas de disciplinas e avaliações através das props descritas na secção Propriedades e Eventos.

Propriedades e Eventos

A API do componente foi desenhada para ser flexível, podes utilizar o calendário com dados internos (padrão) ou passar dados e controladores externos para integração avançada. Abaixo está a lista exaustiva de propriedades (props), eventos (emits) e métodos expostos.

Resumo rápido da API
  • Props: viewMode, disciplinasData, initialDate, locale, config e mais.
  • Emits / Callbacks: day-click, month-change, filter-change.
  • Métodos: possibilidade de aceder a funções reativas via ref (ex.: goToDate, refreshData).

Assinatura TypeScript

// Tipos principais usados pelo componente
type Avaliacao = {
  data: string; // ISO date 'YYYY-MM-DD'
  descricao: string;
  disciplina?: string;
  slug?: string;
};

type Disciplina = {
  slug: string;
  nome: string;
  avaliacoes: Avaliacao[];
};

interface CalendarConfig {
  firstWeekDay?: number; // 0 = Domingo, 1 = Segunda (padrão usado: 1)
  semesterRange?: { startMonth: number; endMonth: number }; // ex: { startMonth: 8, endMonth: 1 } para set-fev
  heatmapBucketCount?: number; // número de níveis de cor
  maxHeatmapWeeks?: number;
  dateAdapter?: 'native' | 'date-fns' | 'dayjs';
}

// Props expostas
interface CalendarProps {
  viewMode?: 'calendar' | 'list' | 'heatmap';
  disciplinasData?: Disciplina[];
  initialDate?: string | Date;
  locale?: string; // ex: 'pt-PT'
  config?: CalendarConfig;
  showLegend?: boolean;
  compact?: boolean;
  // Callbacks
  onDayClick?: (date: string, avaliacoes: Avaliacao[]) => void;
  onViewChange?: (view: 'calendar' | 'list' | 'heatmap') => void;
  onFilterChange?: (selectedSlugs: string[]) => void;
}

// Métodos expostos via ref (opcional)
interface CalendarHandle {
  goToDate: (date: string | Date) => void;
  nextMonth: () => void;
  previousMonth: () => void;
  refreshData: () => Promise<void>;
}

Props detalhadas

Prop Tipo Default Descrição
viewMode 'calendar' | 'list' | 'heatmap' 'calendar' Modo de visualização inicial. Pode ser controlado externamente para implementar layouts específicos.
disciplinasData Array<Disciplina> undefined Fonte dos dados, se não fornecida, o componente carrega ucs.json por defeito. Estrutura: { slug, nome, avaliacoes }.
initialDate string | Date new Date() Data inicial exibida no calendário. Aceita ISO string ('YYYY-MM-DD') ou objeto Date.
locale string 'pt-PT' Localização para formatação de datas. Usado em toLocaleDateString e títulos.
config CalendarConfig Parâmetros avançados: definição do primeiro dia da semana, intervalo do semestre, comportamento do heatmap, e adaptador de datas.
showLegend boolean true Controla a visibilidade da legenda de cores das disciplinas.
compact boolean false Modo compacto para inserção em sidebars ou painéis menores (reduz padding e textos).

Eventos / Callbacks

  • day-click — disparado quando o utilizador clica num dia com avaliações. Callback: (date: string, avaliacoes: Avaliacao[]) => void.
  • month-change — disparado quando o mês alterou (navegação por anterior/próximo). Callback: (year: number, month: number) => void.
  • filter-change — disparado sempre que as disciplinas selecionadas são alteradas. Callback: (selectedSlugs: string[]) => void.
  • view-change — disparado quando o utilizador muda o modo de visualização (calendar/list/heatmap). Callback: (view: 'calendar' | 'list' | 'heatmap') => void.

Exemplo de utilização controlada (props + emits)

<template>
  <Calendar
    :viewMode="view"
    :disciplinasData="disciplinas"
    :initialDate="initialDate"
    @day-click="onDayClick"
    @filter-change="onFilterChange"
    @view-change="onViewChange"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue'
import CalendarComponent from "@/components/Calendar.vue";
import disciplinas from '@/data/ucs.json'

const view = ref<'calendar'|'list'|'heatmap'>('calendar')
const initialDate = ref(new Date())

function onDayClick(date, avaliacoes) {
  // abre um modal personalizado com informações complementares
  console.log('Dia clicado', date, avaliacoes)
}

function onFilterChange(selected) {
  console.log('Disciplinas selecionadas', selected)
}

function onViewChange(v) {
  view.value = v
}
</script>
Notas sobre imutabilidade

Se passares disciplinasData como prop, considera fornecer uma cópia imutável ou utilizar um mecanismo de refresh no componente para evitar que alterações externas desalinhem estados internos (por exemplo: seleção de disciplinas). O componente aceita actualizações reativas, mas algumas optimizações (memoização de mapas por slug) podem exigir uma chamada a refreshData().

Componentes Internos

O componente principal é composto por vários subcomponentes e unidades lógicas que suportam a experiência: Painel de filtros, Vista de Calendário (grid de dias), Vista de Lista, Heatmap Semestral, Diálogo de detalhe e utilitários auxiliares. Abaixo descrevemos cada um deles, com responsabilidades, API interna e pontos de atenção.

Filtros de Disciplinas

O painel de filtros permite selecionar quais as disciplinas que aparecem nas vistas. É composto por um componente de selecção (Checkbox), um botão para seleccionar/desseleccionar todas e uma área scrollable com a lista de disciplinas.

Responsabilidades
  • Manter o estado de seleção por slug.
  • Expor um estado "todasSelecionadas" (boolean) e "algumasSelecionadas" (indeterminado).
  • Emitir filter-change com a lista de slugs seleccionados.
  • Mostrar um indicador numérico de avaliações por disciplina e por mês (estatísticas).
Props internas
  • disciplinas: Disciplina[] — lista completa.
  • selectedMap: Record<string, boolean> — mapa de selecção por slug.
  • onToggle(slug: string) — callback para alternar seleção.
Exemplo: implementação minimal (pseudo)
<template>
  <div class="filters">
    <Checkbox v-model="allSelected" :indeterminate="someSelected" />
    <div class="list">
      <div v-for="d in disciplinas" :key="d.slug" class="item">
        <Checkbox :id="d.slug" v-model="selectedMap[d.slug]" />
        <label :for="d.slug">{{ d.nome }} ({{ d.avaliacoes.length }})</label>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
defineProps({
  disciplinas: Array,
  selectedMap: Object,
})
// lógica: allSelected, someSelected, watchers para emitir filter-change
</script>
Acessibilidade

As checkboxes devem usar atributos aria-checked e labels associados com for. Em dispositivos móveis, o painel de filtros abre como Sheet para ocupar ecrã completo.

Vista: Calendário (Grid Mensal)

A vista de calendário é responsável por:

  • Gerar a grelha de dias do mês com placeholders para os dias anteriores ao primeiro dia do mês.
  • Exibir micro-blocos que representam avaliações por dia, com limite de itens visíveis e indicador "+N".
  • Destacar o dia actual e aplicar estilos de foco/hover para interacção.
  • Expor clique no dia para abrir o diálogo de detalhes.
Algoritmos importantes
  1. getFirstDayOfMonth(date): calcula deslocamento para alinhar segunda-feira como primeiro dia da semana (consistente com PT).
  2. calendarDays: constrói o array de (Date|null) com placeholders.
  3. getAvaliacoesForDate(date): lookup por string ISO, optimizado com um Map<dateStr, Avaliacao[]> para O(1) por dia.
Estrutura do DOM e classes utilitárias

Cada célula do calendário é um botão semânticamente interativo (ou div com role="button") com:

  • aria-label com data formatada e número de avaliações.
  • Indicadores visuais por disciplina (bolinhas ou badges).
  • Classes responsivas para versão mobile/desktop.
// Optimização sugerida (pseudo)
const avaliacoesMap = computed(() => {
  const map = new Map()
  avaliacoes.forEach(a => {
    map.set(a.data, (map.get(a.data) || []).concat(a))
  })
  return map
})

function getAvaliacoesForDate(date) {
  const key = date.toISOString().split('T')[0]
  return avaliacoesMap.value.get(key) || []
}
Performance

Para meses com elevada densidade de avaliações (ex.: centenas por mês), evita renderizações desnecessárias: usa v-for com :key estável, memoiza lookups e limita o número de items renderizados por célula.

Vista: Lista Cronológica

A vista em lista é destinada a leitura sequencial, útil para estudantes que querem ver as próximas avaliações ordenadas por data e agrupadas por mês.

Comportamento
  • Ordena por data ascendente (mais próximo primeiro).
  • Agrupa por mês (ex.: "Setembro 2025").
  • Cada item apresenta: dia, mês abreviado, nome da disciplina, descrição e badge opcional.
  • Suporta infinite-scroll ou altura fixa com ScrollArea para usabilidade em listas longas.
// Agrupar por mês (já implementado no componente)
const agruparPorMes = (lista) => {
  const grupos = {}
  lista.forEach(a => {
    const data = new Date(a.data + 'T00:00:00')
    const mesAno = data.toLocaleDateString('pt-PT', { month: 'long', year: 'numeric' })
    if (!grupos[mesAno]) grupos[mesAno] = []
    grupos[mesAno].push(a)
  })
  return grupos
}

Vista: Heatmap Semestral

O heatmap apresenta semanas como linhas (ou colunas, dependendo da implementação) e dias como células, colore cada célula segundo o número de avaliações desse dia. É orientado para identificar picos de carga.

Construção do semestre

O heatmap é gerado com base no período de um semestre académico definido por startMonth e endMonth. A implementação calcula todas as semanas entre as datas de início e fim e preenche dias nulos com placeholders.

Intensidade e buckets

A cor de cada célula é determinada por um valor normalizado: intensity = count / maxCount. O componente fornece utilitários para mapear essa intensidade para classes de CSS (ex: bg-chart-1/30).

function getHeatmapIntensity(count: number, max: number) {
  if (count === 0) return 'bg-muted'
  const intensity = count / max
  if (intensity <= 0.25) return 'bg-chart-1/30'
  if (intensity <= 0.5) return 'bg-chart-1/50'
  if (intensity <= 0.75) return 'bg-chart-1/70'
  return 'bg-chart-1'
}
Legibilidade

Para manter a legibilidade do heatmap em ecrãs pequenos, mostra apenas marcadores numéricos ao hover/tap e fornece a legenda de intensidades mais em baixo.

Dialog / Details

O pop-up de detalhe é invocado ao clicar num dia com avaliações. É implementado com o componente Dialog e apresenta a lista de avaliações com os campos: disciplina, descrição e data formatada.

Requisitos de acessibilidade
  • Foco inicial no título do pop-up.
  • Fecho com Escape e botão explícito de fechar.
  • Leitura linear por leitores de ecrã; elementos com role="dialog" e aria-modal="true".
<Dialog v-model:open="dialogOpen">
  <DialogContent>
    <DialogHeader>
      <DialogTitle>{{ selectedDateFormatted }}</DialogTitle>
    </DialogHeader>
    <div v-if="avaliacoes.length">
      <div v-for="a in avaliacoes" :key="a.descricao" class="p-3 border rounded">
        <h4 class="font-semibold">{{ a.disciplina }}</h4>
        <p class="text-sm">{{ a.descricao }}</p>
        <p class="text-xs text-muted-foreground">{{ formatarData(a.data) }}</p>
      </div>
    </div>
  </DialogContent>
</Dialog>

Utilitários e Helpers

O componente inclui vários helpers pequenas funções que encapsulam lógica de datas, formatação e mapear disciplinas para cores.

Principais funções
  • formatarData(dateStr) — formata para 'dd de mês de aaaa' em pt-PT.
  • formatarDataRelativa(dateStr) — devolve 'hoje', 'amanhã', 'há X dias', 'daqui a X semanas', etc.
  • getCorDisciplina(slug) — devolve uma classe CSS colorida com mapeamento estável por índice.
  • agruparPorMes(lista) — agrupa avaliações por label 'Mês Ano'.
// Exemplo: formatarData
function formatarData(dataStr: string) {
  const data = new Date(dataStr + 'T00:00:00')
  return data.toLocaleDateString('pt-PT', {
    day: '2-digit',
    month: 'long',
    year: 'numeric',
  })
}

Estados e Lógica Interna

O componente Calendário é altamente reativo. Toda a sua arquitetura interna é baseada em ref, computed e watch da Composition API. O estado principal é mínimo e derivável, permitindo uma renderização previsível e consistente, mesmo sob carga intensa (ex.: dezenas de disciplinas e centenas de avaliações).

Resumo do estado principal
  • currentDate: data atual exibida (mês/ano corrente).
  • viewMode: modo de visualização ativo — 'calendar', 'list' ou 'heatmap'.
  • selectedSlugs: disciplinas atualmente visíveis no filtro.
  • avaliacoesFiltradas: avaliações já filtradas, usadas nas vistas.
  • avaliacoesMap: cache (Map) de avaliações por data.
  • dialogState: estado reativo de abertura/fecho e data selecionada.
  • config: configuração normalizada, combinando defaults com props.

Estrutura geral do script setup

<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import { parseISO, format, addMonths, subMonths } from 'date-fns'
import { pt } from 'date-fns/locale'

import type { Disciplina, Avaliacao, CalendarConfig } from './types'

const props = defineProps<CalendarProps>()
const emit = defineEmits(['day-click', 'month-change', 'filter-change', 'view-change'])

// Estado principal
const currentDate = ref(props.initialDate ? new Date(props.initialDate) : new Date())
const viewMode = ref(props.viewMode || 'calendar')
const selectedSlugs = ref<string[]>([])
const dialogOpen = ref(false)
const selectedDay = ref<string | null>(null)

// Computed: configuração normalizada
const config = computed<CalendarConfig>(() => ({
  firstWeekDay: props.config?.firstWeekDay ?? 1,
  semesterRange: props.config?.semesterRange ?? { startMonth: 8, endMonth: 1 },
  dateAdapter: props.config?.dateAdapter ?? 'date-fns',
  heatmapBucketCount: props.config?.heatmapBucketCount ?? 5,
  maxHeatmapWeeks: props.config?.maxHeatmapWeeks ?? 24,
}))

// Computed: disciplinas ativas
const disciplinasAtivas = computed(() => {
  if (!props.disciplinasData) return []
  if (selectedSlugs.value.length === 0) return props.disciplinasData
  return props.disciplinasData.filter((d) => selectedSlugs.value.includes(d.slug))
})

// Computed: avaliações filtradas
const avaliacoesFiltradas = computed<Avaliacao[]>(() => {
  return disciplinasAtivas.value.flatMap((d) => d.avaliacoes)
})

// Cache: mapa de avaliações por data (ISO)
const avaliacoesMap = computed(() => {
  const map = new Map<string, Avaliacao[]>()
  avaliacoesFiltradas.value.forEach((a) => {
    if (!map.has(a.data)) map.set(a.data, [])
    map.get(a.data)!.push(a)
  })
  return map
})

// Watchers
watch(viewMode, (v) => emit('view-change', v))
watch(selectedSlugs, (s) => emit('filter-change', s))

function goToNextMonth() {
  currentDate.value = addMonths(currentDate.value, 1)
  emit('month-change', currentDate.value.getFullYear(), currentDate.value.getMonth() + 1)
}

function goToPreviousMonth() {
  currentDate.value = subMonths(currentDate.value, 1)
  emit('month-change', currentDate.value.getFullYear(), currentDate.value.getMonth() + 1)
}

function onDayClick(dateStr: string) {
  const avaliacoes = avaliacoesMap.value.get(dateStr) || []
  emit('day-click', dateStr, avaliacoes)
  selectedDay.value = dateStr
  dialogOpen.value = true
}
</script>

Gestão de estado derivado

Nenhum dado é duplicado. O estado derivado é obtido dinamicamente, minimizando inconsistências:

  • avaliacoesFiltradas depende de disciplinasAtivas.
  • avaliacoesMap depende de avaliacoesFiltradas.
  • heatmapData depende de avaliacoesMap e config.
Reatividade previsível

Esta hierarquia reativa assegura que o calendário nunca precisa de deep watch em listas complexas, todas as dependências são computadas a partir de fontes únicas, o que evita loops ou actualizações desnecessárias.

Decisões de Design

O desenho do componente foi orientado por princípios de consistência visual, acessibilidade, desempenho e manutenção. As decisões abaixo documentam os principais trade-offs técnicos e UX considerados.

1. Consistência visual

Foi seguida a linha de design dos componentes shadcn/ui, priorizando legibilidade e hierarquia visual: cores neutras, foco claro e interacções suaves. O calendário herda as variáveis do tema definido.

2. Acessibilidade (A11Y)

  • Todos os elementos interativos possuem role e aria-label.
  • Navegação por teclado: setas movem o foco entre dias, Enter ativa o diálogo.
  • Dialog implementado com foco cíclico e retorno automático após fecho.

3. Desempenho

  • Pré-cálculo de mapas de avaliações (Map<string, Avaliacao[]>) para lookup O(1).
  • Uso intensivo de computed em vez de watchers manuais.
  • Lazy rendering em heatmap e lista (v-if condicional fora da viewport).

4. UX e microinterações

A navegação mensal é suave: o calendário realiza uma transição animada entre meses, evitando o salto abrupto. As células de dia são clicáveis, mas também focáveis, garantindo que o utilizador pode navegar só com teclado.

5. Modularidade

Todas as vistas (CalendarView, ListView, HeatmapView) são componentes independentes. Isto permite manter responsabilidades isoladas e facilita futuras extensões (ex.: "WeekView" ou "AgendaView").

Separação de preocupações

Evita inserir lógica de dados (fetch, transformação) diretamente na UI. O calendário deve receber dados prontos ou delegar o carregamento ao módulo de integração (ucs.json).

Integração com o Projeto

O componente está desenhado para integrar-se diretamente com os dados definidos em ucs.json, um ficheiro que contém a estrutura de todas as unidades curriculares, incluindo avaliações, datas e metadados adicionais.

Estrutura do ficheiro ucs.json

[
  {
    "slug": "matematica-i",
    "nome": "Matemática I",
    "avaliacoes": [
      { "data": "2025-09-24", "descricao": "Teste 1" },
      { "data": "2025-11-02", "descricao": "Exame final" }
    ]
  },
  {
    "slug": "fisica-i",
    "nome": "Física I",
    "avaliacoes": [
      { "data": "2025-10-13", "descricao": "Mini-teste" }
    ]
  }
]

O formato é simples, mas consistente. Cada disciplina é identificada por um slug único, usado internamente no calendário para gerar cores e filtros estáveis.

Importação e validação

O ficheiro é importado diretamente via módulo estático, o que permite bundling e tipagem. Contudo, pode também ser carregado dinamicamente (fetch). O calendário valida a estrutura básica dos dados antes de processá-los.

import ucsData from '@/data/ucs.json'

function validarUCS(data: any): data is Disciplina[] {
  if (!Array.isArray(data)) return false
  return data.every((uc) => uc.slug && uc.nome && Array.isArray(uc.avaliacoes))
}

if (!validarUCS(ucsData)) {
  console.error('Estrutura de ucs.json inválida')
} else {
  console.log('Dados validados:', ucsData.length, 'disciplinas')
}
Validação leve mas eficaz

Não é necessário validar profundamente cada campo, mas garantir que os campos estruturais principais estão presentes evita erros de parsing e datas inválidas.

Exemplo de integração direta

<template>
  <div class="p-6">
    <Calendar :disciplinasData="ucsData" @day-click="abrirDetalhes" />
  </div>
</template>

<script setup lang="ts">
import CalendarComponent from "@/components/Calendar.vue";
import ucsData from '@/data/ucs.json'

function abrirDetalhes(date, avaliacoes) {
  console.log('Avaliações nesse dia:', date, avaliacoes)
}
</script>

Este exemplo mostra a integração típica no projeto, o componente é reativo e funciona imediatamente ao receber a estrutura ucs.json.

Boas Práticas e Recomendações

O componente foi projetado para ser reutilizável, extensível e fácil de manter. As boas práticas abaixo ajudam a garantir consistência e longevidade no projeto.

1. Tipagem e Segurança

Define sempre os tipos Disciplina, Avaliacao e CalendarConfig explicitamente. Isto evita erros silenciosos e melhora a autocompletação no editor.

2. Comunicação de Eventos

Usa defineEmits com eventos semanticamente claros. Evita enviar objetos excessivamente aninhados ou dependentes de estado interno.

3. Acessibilidade

  • Evita dependências de mouse-only, testa com teclado.
  • Usa labels descritivos e feedback auditivo (screen readers).
  • Verifica contraste de cores e tamanhos de fonte.

4. Desempenho e Lazy Loading

Carrega apenas a vista necessária, v-if sobre o modo ativo. O heatmap e lista não devem ser montados até o utilizador os ativar.

5. Persistência e Preferências

Podes armazenar as preferências de filtro e modo de visualização no localStorage (embora para já não seja implementado nativamente):

watch(viewMode, (v) => localStorage.setItem('calendar-view', v))
onMounted(() => {
  const saved = localStorage.getItem('calendar-view')
  if (saved) viewMode.value = saved
})
Escalabilidade

À medida que o número de disciplinas cresce, o calendário mantém-se estável porque usa computed e Map para operações de O(1). O heatmap, em particular, escala para mais de 500 avaliações sem perda de fluidez perceptível.

7. Deploy e Internacionalização

Usa date-fns com locale configurável. Para novos idiomas, basta importar e ajustar o locale:

import { es } from 'date-fns/locale'
const config = { ...defaultConfig, locale: es }
Personalização global

Todos os subcomponentes do calendário aceitam class e style props para override. Assim, é possível adaptá-los ao tema da instituição sem alterar a lógica base.

Extensões Futuras / Roadmap

O Calendário foi concebido como base sólida de visualização e gestão de avaliações. No entanto, existem diversas áreas de expansão já mapeadas para versões futuras, alinhadas com a evolução do ecossistema Vue e das necessidades.

1. Vistas adicionais

  • WeekView: visão semanal compacta, ideal para planeamento detalhado.
  • AgendaView: listagem contínua de avaliações futuras, agrupadas por disciplina.
  • TimelineView: linha temporal horizontal com marcações por tipo de avaliação.

2. Sincronização externa

  • Exportação para iCalendar (.ics), Google Calendar e sincronização bidirecional.
  • Integração com APIs institucionais (ex.: SIGARRA, Moodle, Google Classroom).
  • Webhook para notificações automáticas de alterações de datas.

3. Colaboração

Permitir que professores e estudantes adicionem anotações ou lembretes pessoais diretamente no calendário, sem afetar os dados oficiais.

4. Inteligência Adaptativa

Planeia-se a introdução de um módulo preditivo, capaz de sugerir datas de estudo ideais com base na densidade de avaliações e desempenho histórico do aluno.

5. Extensão para dispositivos móveis

  • Gestos de swipe para navegar entre meses.
  • Modo compacto adaptativo para ecrãs pequenos.
  • Cache local e modo offline (PWA-ready).

6. Observabilidade e Telemetria

Implementação futura de métricas de uso anónimas, recolhendo eventos como mudança de vista, número médio de avaliações carregadas e frequência de acessos. Estas métricas ajudarão a priorizar otimizações reais, baseadas em uso.

Gestão de versões

Cada extensão relacionada deve ser lançada sob um sistema de versionamento semântico: major.minor.patch. As alterações de API pública futuramente serão documentadas em changelogs no diretório docs/changelogs/.

7. Interoperabilidade futura

Uma das metas de médio prazo é converter o núcleo do calendário num componente web independente (<academic-calendar>), permitindo a integração com projetos não-Vue, como Astro puro, React ou Svelte.

Versão do documento: 1.0.0 • Última atualização: Outubro 2025 — Infoloom