Addon Categories

Punch

Integrate corporate time-clock systems to record and review working hours.

Punch (Time Clocks & Working Hours)

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.

Punch addons integrate Mr. Tick with formal time-clock and attendance systems such as Pontomais, Ahgora, Factorial, Secullum, and Kairos.

Punch allows users to clock in directly from Mr. Tick and automatically review working hours, comparing the day's attendance balance with time allocated to tasks and projects.


🎯 Main Features

  • Quick Clock In/Out: Use a context-menu button or keyboard shortcut to record arrival, breaks, and departure.
  • Hours Audit (Attendance vs. Time Entries): Show the difference between formal working hours and time logged against DataSource tasks to avoid inconsistencies at the end of the month.
  • Attendance History: View the daily clock-in/out record directly in Mr. Tick.

💻 Punch Implementation Example

1. manifest.yaml

id: ponto-corporativo
name: Ponto Corporativo Integration
version: 1.0.0
description: Bate-ponto integrado com conferência de jornada
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 PunchRecordDTO {
  id: string
  timestamp: Date
  type: 'in' | 'out' | 'break_start' | 'break_end'
  source: string
}

export default class PontoCorporativoAddon 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: 'ponto_auth',
            label: 'Configurações de Acesso',
            fields: [
              {
                id: 'pontoApiUrl',
                label: 'URL da API de Ponto',
                type: 'text',
                scope: 'configuration',
                required: true,
                description: 'Endereço da API do sistema de ponto da empresa.',
              },
              {
                id: 'pontoToken',
                label: 'Token de Acesso / Chave do Colaborador',
                type: 'password',
                scope: 'credential',
                required: true,
              },
            ],
          },
        ],
      },
    ])

    console.log('[Punch] Addon de Ponto ativado!')
  }

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

  // Executa uma nova batida de ponto
  public async registerPunch(
    type: 'in' | 'out' | 'break_start' | 'break_end',
    workspaceId: string,
  ): Promise<Either<AppError, PunchRecordDTO>> {
    if (this.context === null) return Either.failure(AppError.Internal('ADDON_NOT_ACTIVE'))
    const storedUrl = await this.context.host.vault.get(
      { kind: 'workspace', workspaceId }, 'pontoApiUrl',
    )
    if (storedUrl.isFailure()) return storedUrl.forwardFailure()
    const storedToken = await this.context.host.vault.get(
      { kind: 'workspace', workspaceId }, 'pontoToken',
    )
    if (storedToken.isFailure()) return storedToken.forwardFailure()
    const apiUrl = storedUrl.success
    const token = storedToken.success
    if (apiUrl === null || token === null) return Either.failure(AppError.ValidationError('PUNCH_CONFIG_REQUIRED'))

    const response = await fetch(`${apiUrl}/punches`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify({
        type,
        timestamp: new Date().toISOString(),
      }),
    })

    if (!response.ok) return Either.failure(AppError.Http(response.status, await response.text()))

    const data: { id: string; timestamp: string } = await response.json()

    await this.context?.host.notifications.success(
      `Batida de ponto (${type}) realizada com sucesso!`,
      'Ponto Registrado',
    )

    return Either.success({
      id: data.id,
      timestamp: new Date(data.timestamp),
      type,
      source: 'Mr-tick Desktop',
    })
  }
}