Addon Categories

Calendars

Integrate external calendars (Google Calendar, Outlook) to synchronize appointments and turn them into tracked time.

Calendars (Schedules & Meetings)

This category example is conceptual and uses old contracts. Do not copy its APIs as a reference for the current SDK; consult Vault and Events and the contracts exported by @mr-tick/sdk.

Calendars addons connect Mr. Tick to providers such as Google Calendar, Microsoft Outlook / 365, and iCal calendars.

This integration displays daily appointments in Mr. Tick's calendar view, allowing users to turn past meetings into actual time entries with one click.


🎯 Calendar Event Structure

To populate Mr. Tick's interface, a calendar plugin normalizes external events into a standard structure:

export interface CalendarEventDTO {
  id: string // Identificador único do evento no provedor externo
  title: string // Título ou assunto da reunião
  description?: string // Pauta ou descrição do evento
  startDate: Date // Data e hora de início
  endDate: Date // Data e hora de término
  attendeesCount?: number // Quantidade de participantes
  meetLink?: string // Link da chamada (Google Meet, Teams, Zoom)
  isAllDay?: boolean // Indica se é um evento de dia inteiro
}

💻 Calendar Implementation Example

1. manifest.yaml

id: google-calendar-addon
name: Google Calendar Integration
version: 1.0.0
description: Sincroniza reuniões do Google Calendar com o Mr-tick
author: Sua Empresa <contato@empresa.com>
main: dist/index.js

2. src/index.ts

import { AppError, Either } from '@mr-tick/sdk'
import type { IAddon, AddonContext } from '@mr-tick/sdk'

export interface CalendarEventDTO {
  id: string
  title: string
  startDate: Date
  endDate: Date
  meetLink?: string
}

interface GoogleCalendarApiItem {
  id: string
  summary?: string
  start: { dateTime?: string; date?: string }
  end: { dateTime?: string; date?: string }
  hangoutLink?: string
}

interface GoogleCalendarApiResponse {
  items?: GoogleCalendarApiItem[]
}

export default class GoogleCalendarAddon implements IAddon {
  private context: AddonContext | null = null

  public activate(context: AddonContext): void {
    this.context = context

    context.contributions.settings.register([
      {
        id: 'general',
        label: 'Geral',
        groups: [
          {
            id: 'calendar_config',
            label: 'Configurações do Calendário',
            fields: [
              {
                id: 'calendarId',
                label: 'ID do Calendário',
                type: 'text',
                defaultValue: 'primary',
                description:
                  'Identificador do calendário (use "primary" para o principal).',
              },
            ],
          },
        ],
      },
    ])

    console.log('[GoogleCalendar] Addon ativado com sucesso!')
  }

  public deactivate(): void {
    this.context = null
  }

  // Busca os eventos do dia especificado
  public async fetchDayEvents(targetDate: Date, workspaceId: string): Promise<Either<AppError, CalendarEventDTO[]>> {
    if (this.context === null) return Either.failure(AppError.Internal('ADDON_NOT_ACTIVE'))
    const storedToken = await this.context.host.vault.get(
      { kind: 'workspace', workspaceId },
      'accessToken',
    )
    if (storedToken.isFailure()) return storedToken.forwardFailure()
    const accessToken = storedToken.success
    if (accessToken === null) return Either.failure(AppError.Unauthorized('GOOGLE_CALENDAR_AUTH_REQUIRED'))

    const startOfDay = new Date(targetDate)
    startOfDay.setHours(0, 0, 0, 0)

    const endOfDay = new Date(targetDate)
    endOfDay.setHours(23, 59, 59, 999)

    const url = new URL(
      'https://www.googleapis.com/calendar/v3/calendars/primary/events',
    )
    url.searchParams.set('timeMin', startOfDay.toISOString())
    url.searchParams.set('timeMax', endOfDay.toISOString())
    url.searchParams.set('singleEvents', 'true')
    url.searchParams.set('orderBy', 'startTime')

    const response = await fetch(url.toString(), {
      headers: { Authorization: `Bearer ${accessToken}` },
    })

    const data: GoogleCalendarApiResponse = await response.json()

    const events = (data.items || []).map((item: GoogleCalendarApiItem): CalendarEventDTO => ({
      id: item.id,
      title: item.summary || 'Sem título',
      startDate: new Date(item.start.dateTime || item.start.date || targetDate),
      endDate: new Date(item.end.dateTime || item.end.date || targetDate),
      meetLink: item.hangoutLink,
    }))
    return Either.success(events)
  }
}

🚀 Quick Conversion to Time Entries

When a user views meetings in Mr. Tick:

  1. Mr. Tick highlights meetings that have already ended.
  2. The "Log Time" button opens a prefilled form with the meeting title, calculated duration ((endDate - startDate)), and suggested activity.
  3. Saving creates a TimeEntry entity and sends it to the Synchronization Engine.