npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@adatechnology/scheduling-ui

v0.1.0

Published

Scheduling UI — headless hooks + agenda, reservas, recursos, serviços e disponibilidade

Readme

@adatechnology/scheduling-ui

Interface de agendamento modular — tela com navegação por abas, dois hooks reutilizáveis e uma fábrica de componentes. Agenda, reservas, recursos, serviços e regras de disponibilidade em cinco áreas, cada uma com sua grade, filtros e ações.

  • Provider + hooks para consumir a agenda em qualquer página do host
  • SchedulingWorkspace — composição pronta de cinco áreas de trabalho
  • Sem fetch direto — o host implementa SchedulingApi, separando a UI da lógica HTTP
  • Área controlada ou interna — sincroniza com query string (sobrevive a refresh) ou gerencia estado localmente
  • TypeScript completo, React 18+, com i18n português-brasileiro

Instalação

bun add @adatechnology/scheduling-ui
# ou: npm install / pnpm add

Dependências de pares obrigatórias:

bun add react react-dom @tanstack/react-query

O pacote importa tipos de @adatechnology/scheduling-contracts automaticamente (workspace dependency).


Uso básico

import { SchedulingProvider, SchedulingWorkspace, type SchedulingApi } from '@adatechnology/scheduling-ui'

// 1. Implementar SchedulingApi no seu cliente HTTP ou module
const schedulingApi: SchedulingApi = {
  // Recursos
  async listResources(params) {
    const res = await fetch('/api/v1/scheduling/resources', { /* params */ })
    return res.json()
  },
  async createResource(input) {
    const res = await fetch('/api/v1/scheduling/resources', {
      method: 'POST',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async updateResource(id, input) {
    const res = await fetch(`/api/v1/scheduling/resources/${id}`, {
      method: 'PUT',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async deleteResource(id) {
    await fetch(`/api/v1/scheduling/resources/${id}`, { method: 'DELETE' })
  },

  // Serviços
  async listServices(params) {
    const res = await fetch('/api/v1/scheduling/services', { /* params */ })
    return res.json()
  },
  async createService(input) {
    const res = await fetch('/api/v1/scheduling/services', {
      method: 'POST',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async updateService(id, input) {
    const res = await fetch(`/api/v1/scheduling/services/${id}`, {
      method: 'PUT',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async deleteService(id) {
    await fetch(`/api/v1/scheduling/services/${id}`, { method: 'DELETE' })
  },

  // Regras de disponibilidade
  async listAvailabilityRules(resourceId) {
    const res = await fetch(`/api/v1/scheduling/resources/${resourceId}/availability-rules`)
    return res.json()
  },
  async setAvailabilityRules(resourceId, rules) {
    const res = await fetch(`/api/v1/scheduling/resources/${resourceId}/availability-rules`, {
      method: 'PUT',
      body: JSON.stringify(rules),
    })
    return res.json()
  },

  // Exceções de disponibilidade
  async listAvailabilityExceptions(resourceId) {
    const res = await fetch(`/api/v1/scheduling/resources/${resourceId}/availability-exceptions`)
    return res.json()
  },
  async addAvailabilityException(input) {
    const res = await fetch('/api/v1/scheduling/availability-exceptions', {
      method: 'POST',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async removeAvailabilityException(id) {
    await fetch(`/api/v1/scheduling/availability-exceptions/${id}`, { method: 'DELETE' })
  },

  // Disponibilidade de horários
  async getAvailableSlots(params) {
    const res = await fetch('/api/v1/scheduling/available-slots', { /* params */ })
    return res.json()
  },

  // Reservas
  async listBookings(params) {
    const res = await fetch('/api/v1/scheduling/bookings', { /* params */ })
    return res.json()
  },
  async getBooking(id) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}`)
    return res.json()
  },
  async requestBooking(input, idempotencyKey) {
    const res = await fetch('/api/v1/scheduling/bookings', {
      method: 'POST',
      headers: { 'Idempotency-Key': idempotencyKey },
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async confirmBooking(id) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}/confirm`, { method: 'POST' })
    return res.json()
  },
  async rescheduleBooking(id, input) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}/reschedule`, {
      method: 'POST',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async cancelBooking(id, input) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}/cancel`, {
      method: 'POST',
      body: JSON.stringify(input),
    })
    return res.json()
  },
  async completeBooking(id) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}/complete`, { method: 'POST' })
    return res.json()
  },
  async markNoShow(id) {
    const res = await fetch(`/api/v1/scheduling/bookings/${id}/no-show`, { method: 'POST' })
    return res.json()
  },
}

// 2. Envolver o SchedulingWorkspace com o provider
export function SchedulingPage() {
  return (
    <SchedulingProvider api={schedulingApi}>
      <SchedulingWorkspace />
    </SchedulingProvider>
  )
}

O provider e os hooks

SchedulingProvider injeta a API e a configuração no contexto. Componentes internos consomem via dois hooks:

import { useScheduling, useSchedulingConfig } from '@adatechnology/scheduling-ui'

// Dentro de <SchedulingProvider>
export function MyComponent() {
  const api = useScheduling()  // SchedulingApi
  const config = useSchedulingConfig()  // { locale, weekStartsOn }

  // Chamar métodos da API
  const bookings = await api.listBookings()

  return <div>Locale: {config.locale}</div>
}

Os hooks lançam erro se usados fora do provider:

Error: useScheduling() must be used within a <SchedulingProvider>

SchedulingApi

Contrato que o host implementa. O pacote nunca faz fetch direto — chama os métodos que você forneceu via SchedulingProvider.

Nenhum método recebe companyId — o host resolve o tenant do contexto autenticado antes de implementar a API (veja security.md §2, BOLA). Isso deixa o componente agnóstico sobre isolamento multi-empresa.

Atenção — cache do QueryClient na troca de tenant. As chaves de query do pacote (src/hooks/queryKeys.ts) não carregam companyId — elas não têm como, já que a API não o recebe. Se o host reusa a mesma instância de QueryClient para mais de uma empresa (ex.: um operador trocando de tenant sem recarregar a página), dados da empresa anterior podem aparecer em cache até a próxima invalidação. O host deve chamar queryClient.clear() (ou resetQueries()) na troca de tenant, ou criar uma nova instância de QueryClient por sessão de tenant.

Recursos

type SchedulingApi = {
  // Listar com paginação e filtros opcionais
  listResources(params?: Omit<ListResourcesParams, 'companyId'>)
    : Promise<PaginatedResponse<Resource>>

  // Criar novo recurso
  createResource(input: CreateResourceInput): Promise<Resource>

  // Atualizar
  updateResource(id: ResourceId, input: UpdateResourceInput): Promise<Resource>

  // Excluir
  deleteResource(id: ResourceId): Promise<void>
}

Serviços

type SchedulingApi = {
  listServices(params?: Omit<ListServicesParams, 'companyId'>)
    : Promise<PaginatedResponse<Service>>

  createService(input: CreateServiceInput): Promise<Service>

  updateService(id: ServiceId, input: UpdateServiceInput): Promise<Service>

  deleteService(id: ServiceId): Promise<void>
}

Disponibilidade (Regras e Exceções)

type SchedulingApi = {
  // Regras de horário de funcionamento por recurso
  listAvailabilityRules(resourceId: ResourceId): Promise<readonly AvailabilityRule[]>

  setAvailabilityRules(
    resourceId: ResourceId,
    rules: readonly CreateAvailabilityRuleInput[],
  ): Promise<readonly AvailabilityRule[]>

  // Exceções (feriados, bloqueios, etc)
  listAvailabilityExceptions(resourceId: ResourceId)
    : Promise<readonly AvailabilityException[]>

  addAvailabilityException(input: CreateAvailabilityExceptionInput)
    : Promise<AvailabilityException>

  removeAvailabilityException(id: AvailabilityExceptionId): Promise<void>
}

Horários Disponíveis

type SchedulingApi = {
  // Buscar slots livres para um serviço/recurso/período
  getAvailableSlots(params: Omit<GetAvailabilityParams, 'companyId'>)
    : Promise<readonly AvailableSlot[]>
}

Reservas

type SchedulingApi = {
  // Listar reservas do negócio
  listBookings(params?: Omit<ListBookingsParams, 'companyId'>)
    : Promise<PaginatedResponse<Booking>>

  // Buscar uma reserva
  getBooking(id: BookingId): Promise<Booking>

  // Criar reserva. ÚNICO método com idempotencyKey como segundo parâmetro
  requestBooking(input: RequestBookingInput, idempotencyKey: string): Promise<Booking>

  // Confirmar após validação
  confirmBooking(id: BookingId): Promise<Booking>

  // Remarcar
  rescheduleBooking(id: BookingId, input: RescheduleBookingInput): Promise<Booking>

  // Cancelar
  cancelBooking(id: BookingId, input: CancelBookingInput): Promise<Booking>

  // Marcar como completa
  completeBooking(id: BookingId): Promise<Booking>

  // Marcar como não-comparecimento
  markNoShow(id: BookingId): Promise<Booking>
}

Todos os tipos (Resource, Service, Booking, etc) vêm de @adatechnology/scheduling-contracts.


SchedulingWorkspace

Componente com navegação em cinco abas e a composição pronta. Cada aba é uma área:

  • Agenda — visualizar e buscar reservas em grade
  • Reservas — gerenciar reservas (confirmar, remarcar, cancelar)
  • Recursos — criar, editar e excluir recursos
  • Serviços — criar, editar e excluir serviços
  • Disponibilidade — definir regras e exceções por recurso

Uso não-controlado (estado interno)

<SchedulingProvider api={schedulingApi}>
  <SchedulingWorkspace />
</SchedulingProvider>

A aba ativa é guardada no estado interno do componente. Refresh ou link colado volta para a aba padrão (Agenda).

Uso controlado (query string)

Para sincronizar a aba aberta com a URL (e sobreviver a refresh/link):

'use client'

import { useSearchParams, useRouter } from 'next/navigation'
import { SchedulingProvider, SchedulingWorkspace, isSchedulingWorkspaceArea, SCHEDULING_WORKSPACE_AREA } from '@adatechnology/scheduling-ui'

export function SchedulingPage() {
  const router = useRouter()
  const searchParams = useSearchParams()

  const areaFromUrl = searchParams.get('area')
  const area = areaFromUrl && isSchedulingWorkspaceArea(areaFromUrl) 
    ? areaFromUrl
    : SCHEDULING_WORKSPACE_AREA.AGENDA

  function handleAreaChange(nextArea: string) {
    const params = new URLSearchParams(searchParams)
    params.set('area', nextArea)
    router.push(`?${params.toString()}`)
  }

  return (
    <SchedulingProvider api={schedulingApi}>
      <SchedulingWorkspace area={area} onAreaChange={handleAreaChange} />
    </SchedulingProvider>
  )
}

Props

type SchedulingWorkspaceProps = {
  // Sobrescrever rótulos de abas e títulos
  readonly labels?: Partial<SchedulingWorkspaceLabels>

  // Renderizar botões/ações no cabeçalho (ex: exportar agenda)
  readonly renderHeaderActions?: () => ReactNode

  // Aba aberta — torna o componente controlado
  readonly area?: SchedulingWorkspaceArea

  // Chamado quando o usuário clica em uma aba
  readonly onAreaChange?: (area: SchedulingWorkspaceArea) => void
}

Valores válidos para area (constante, não enum — projeto usa as const por convenção):

import { SCHEDULING_WORKSPACE_AREA } from '@adatechnology/scheduling-ui'

const SCHEDULING_WORKSPACE_AREA = {
  AGENDA: 'agenda',
  BOOKINGS: 'bookings',
  RESOURCES: 'resources',
  SERVICES: 'services',
  AVAILABILITY: 'availability',
} as const

Use isSchedulingWorkspaceArea(value) para validar strings da URL:

const area = searchParams.get('area')
if (isSchedulingWorkspaceArea(area)) {
  // TypeScript agora sabe que `area` é válido
}

Configuração

SchedulingProvider aceita uma configuração parcial (config), que mescla com os padrões:

<SchedulingProvider
  api={schedulingApi}
  config={{
    locale: 'pt-BR',      // padrão
    weekStartsOn: 1,      // 0 = domingo, 1 = segunda-feira (padrão)
  }}
>
  <SchedulingWorkspace />
</SchedulingProvider>
  • locale — identifica o idioma; usado em i18n dentro do componente. Padrão: 'pt-BR'.
  • weekStartsOn — primeiro dia da semana na grade de agenda (0 = domingo, 1 = segunda). Padrão: 1.

Licença

MIT © Ada Technology