---
title: "Custom Calendar (Week)"
slug: "custom-calendar-week"
category: "Widgets"
reactComponent: "ArcWidgetCalendarWeek"
reactTier: "premium"
reactSince: "0.1.0-alpha.5"
reactExample: "calendar_week.tsx"
locale: "es"
hosts: "react"
---

# CalendarWeek en React

Muestra citas en una vista semanal con cuadrícula horaria. El ejemplo mantiene la selección y las citas en la aplicación.

Referencia para `@arcrider/arcwidgets-react@0.1.0-alpha.5`. [Instalación](/docs/react/intro/setup-react) · [Todos los componentes](/docs/react/intro/react-components).

## Ejemplo completo

```tsx
import { ArcWidgetCalendarWeek } from '@arcrider/arcwidgets-react';
import { useState } from 'react';

const lang = document.documentElement.lang.startsWith('en')
  ? 'en'
  : document.documentElement.lang.startsWith('es')
    ? 'es'
    : 'de';
const requested = new URLSearchParams(location.search).get('data');
const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo';
const t = (de: string, en: string, es: string) =>
  lang === 'en' ? en : lang === 'es' ? es : de;
const locale = lang === 'en' ? 'en-US' : lang === 'es' ? 'es-ES' : 'de-DE';
const day = (offset: number) => new Date(2026, 8, 14 + offset).getTime();
const hour = (value: number) => value * 3600000;

const chrome = {
  backgroundColor: '#ffffff',
  fontColor: '#18181b',
  mutedColor: '#52525b',
  borders: { color: '#e4e4e7', outer: true, columns: true, rows: true },
};

const look = (backgroundColor: string, color: string) => ({
  backgroundColor,
  color,
  fontColor: color,
});

const teal = '#115e59';
const tealFill = '#ccfbf1';
const travelFill = '#99f6e4';
const pauseFill = '#f4f4f5';
const pauseInk = '#52525b';

type Entry = {
  id: string;
  title: string;
  dateFrom: number;
  dateTo: number;
  timeFrom: number;
  timeTo: number;
  clickable: true;
  draggable: true;
  styles: ReturnType<typeof look>;
  insetBorder: { enabled: true; color: string };
  segments?: Array<{
    title: string;
    linked?: boolean;
    dateFrom: number;
    dateTo: number;
    timeFrom: number;
    timeTo: number;
    styles: ReturnType<typeof look>;
    insetBorder: { enabled: true; color: string };
    splits?: Array<{
      title: string;
      timeFrom: number;
      timeTo: number;
      styles: ReturnType<typeof look>;
    }>;
  }>;
};

const event = (
  id: string,
  title: string,
  offset: number,
  from: number,
  to: number,
  backgroundColor: string,
  color: string,
): Entry => ({
  id,
  title,
  dateFrom: day(offset),
  dateTo: day(offset),
  timeFrom: hour(from),
  timeTo: hour(to),
  clickable: true,
  draggable: true,
  styles: look(backgroundColor, color),
  insetBorder: { enabled: true, color },
});

const siteVisit = (): Entry => ({
  id: 'visit',
  title: t('Kundentermin', 'Site visit', 'Visita'),
  dateFrom: day(3),
  dateTo: day(3),
  timeFrom: hour(11),
  timeTo: hour(14),
  clickable: true,
  draggable: true,
  styles: look(tealFill, teal),
  insetBorder: { enabled: true, color: teal },
  segments: [
    {
      title: t('Anfahrt', 'Travel there', 'Ida'),
      linked: false,
      dateFrom: day(2),
      dateTo: day(2),
      timeFrom: hour(16),
      timeTo: hour(17),
      styles: look(travelFill, teal),
      insetBorder: { enabled: true, color: teal },
    },
    {
      title: t('Hauptarbeit', 'On site', 'Trabajo principal'),
      linked: true,
      dateFrom: day(3),
      dateTo: day(3),
      timeFrom: hour(11),
      timeTo: hour(14),
      styles: look(tealFill, teal),
      insetBorder: { enabled: true, color: teal },
      splits: [
        {
          title: t('Pause', 'Break', 'Pausa'),
          timeFrom: hour(12) + 30 * 60 * 1000,
          timeTo: hour(13),
          styles: look(pauseFill, pauseInk),
        },
      ],
    },
    {
      title: t('Abfahrt', 'Travel back', 'Vuelta'),
      linked: false,
      dateFrom: day(4),
      dateTo: day(4),
      timeFrom: hour(8),
      timeTo: hour(9),
      styles: look(travelFill, teal),
      insetBorder: { enabled: true, color: teal },
    },
  ],
});

const demoEntries: Entry[] = [
  event(
    'plan',
    t('Planung', 'Planning', 'Planificación'),
    0,
    9,
    10,
    '#d1fae5',
    '#065f46',
  ),
  event(
    'workshop',
    t('Workshop', 'Workshop', 'Taller'),
    1,
    10,
    12,
    '#fef3c7',
    '#92400e',
  ),
  event(
    'review',
    t('Review', 'Review', 'Revisión'),
    2,
    14,
    15,
    '#ffe4e6',
    '#9f1239',
  ),
  siteVisit(),
];

const busyEntries: Entry[] = [
  ...demoEntries,
  event(
    'stand',
    t('Standabnahme', 'Booth check', 'Revisión stand'),
    4,
    10,
    12,
    '#ede9fe',
    '#5b21b6',
  ),
  event('retro', t('Retro', 'Retro', 'Retro'), 4, 15, 16, '#e0f2fe', '#075985'),
];

export default function Example() {
  const [entries, setEntries] = useState(
    pack === 'empty' ? [] : pack === 'busy' ? busyEntries : demoEntries,
  );
  const [selected, setSelected] = useState(
    t(
      'Termin ziehen oder anklicken',
      'Drag or click an event',
      'Arrastra o pulsa un evento',
    ),
  );
  return (
    <>
      <ArcWidgetCalendarWeek
        id="week"
        data={{
          timeZoneBalance: 0,
          height: '480px',
          lang,
          styles: chrome,
          dateSettings: {
            startDate: day(0),
            duration: 7,
            day: { clickable: true },
          },
          timeSettings: {
            timeStart: hour(8),
            duration: hour(10),
          },
          timeEntries: entries,
        }}
        onEntryClick={() =>
          setSelected(
            t('Termin ausgewählt', 'Event selected', 'Evento seleccionado'),
          )
        }
        onDayClick={(date) =>
          setSelected(new Date(date).toLocaleDateString(locale))
        }
        onEntryDragSave={(index, start) => {
          setEntries((previous) => {
            if (index >= previous.length || previous[index].segments)
              return previous;
            return previous.map((entry, i) =>
              i === index
                ? {
                    ...entry,
                    dateFrom: Number(start.dateFrom ?? entry.dateFrom),
                    dateTo: Number(start.dateTo ?? entry.dateTo),
                    timeFrom: Number(start.timeFrom ?? entry.timeFrom),
                    timeTo: Number(start.timeTo ?? entry.timeTo),
                  }
                : entry,
            );
          });
          setSelected(t('Termin verschoben', 'Event moved', 'Evento movido'));
        }}
      />
      <p aria-live="polite">{selected}</p>
    </>
  );
}
```

## Props del componente

### id

ID opcional y estable de la instancia del widget. Sin especificarlo, React genera un ID.

### data

Configuración de tipo `ArcWidgetCalendarWeekData`. Pasa los cambios como objetos nuevos; gestiona los datos de negocio y la selección en el estado de React.

### onAction

Fallback opcional `(type: string, ...args: unknown[]) => unknown`. El callback concreto tiene prioridad. Detalles: [Estado y eventos](/docs/react/patterns/react-state-events).

### onEntryClick

Índice de la cita, y en Timeline también la fila del recurso.

```typescript
onEntryClick?: (entry_index: number) => void;
```

### onDayClick

Fecha del día seleccionado como marca de tiempo en milisegundos.

```typescript
onDayClick?: (date_millis: number) => void;
```

### onEntryDragSave

Fin del arrastre con los datos originales. No hay horas de destino completas nuevas; consulta las notas sobre persistencia.

```typescript
onEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarWeekDragStart) => void;
```

### onDailyEntryDragSave

Fin del arrastre de una cita de todo el día con los datos originales.

```typescript
onDailyEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarWeekDragStart) => void;
```

### onCreate

Nueva selección de fecha/hora. La validación y el guardado los realiza la aplicación.

```typescript
onCreate?: (values: {
        dateFrom?: number;
        dateTo?: number;
        timeFrom?: number;
        timeTo?: number;
        [key: string]: unknown;
    }) => void;
```

## Configuración

Los siguientes campos pertenecen a `data`. Los tamaños se indican como strings, los números y valores de tiempo según su tipo respectivo.

### height

Altura del widget, p. ej. `"320px"`. Los valores porcentuales necesitan un contenedor padre con altura definida.

Tipo: `string`.

### styles

Colores, espaciados y bordes comunes; los campos ofrecidos son específicos del widget.

Tipo: `ArcWidgetCalendarWeekStyles | string`.

### weekdaySettings

Selección de una semana calendario y del año.

Tipo: `{ week?: number; year?: number; [key: string]: unknown }`.

### weekStart

Inicio de semana alternativo.

Tipo: `unknown`.

### dateSettings

Rango de fechas, cabeceras y marcados.

Tipo: `ArcWidgetCalendarDateSettings`.

### timeZoneBalance

Corrección en horas; los ejemplos usan `0` y valores de día generados localmente.

Tipo: `number | string`.

### timeSettings

Rango horario y cuadrícula; las unidades son milisegundos.

Tipo: `ArcWidgetCalendarTimeSettings`.

### currentTime

Representación de la hora actual.

Tipo: `string`.

### lang

Idioma de las etiquetas del calendario.

Tipo: `string`.

### timeSlotWidth

Ancho de una columna de día.

Tipo: `string`.

### timeSlotHeight

Altura de una franja horaria.

Tipo: `string`.

### allDayHeight

Altura del área para citas de todo el día.

Tipo: `string`.

### createNew

Selección para crear nuevas citas. El guardado se realiza mediante `onCreate`.

Tipo: `ArcWidgetCalendarCreateNew`.

### timeEntries

Citas con valores de fecha, horas y contenidos.

Tipo: `ArcWidgetCalendarTimeEntry[]`.

## Comportamiento y límites

- Para JSX en citas usa `customLayout`. Los campos estándar `title`, `subtitle` y `value` se procesan como texto o HTML.

- Los valores de fecha son marcas de tiempo en milisegundos; las horas son milisegundos desde el inicio del día. Consulta [Fecha y hora](/docs/react/patterns/react-dates).

- `clickable: true` activa la selección. Un índice de callback no es un ID de registro; resuélvelo mediante tu lista actual de entradas.

- Los callbacks de arrastre actuales contienen el estado original (`start_data`), pero no horas de destino completas nuevas. Por eso el ejemplo establece `draggable: false`. Edita las citas mediante el estado de React hasta que exista un contrato de persistencia completo para arrastrar y soltar.

- El rango horario visible se configura mediante `timeSettings.timeStart` y `timeSettings.duration` en milisegundos. Los campos históricos `from` y `to` no tienen efecto aquí.

- `onCreate` entrega los valores de la selección. Tu aplicación los valida, asigna un ID y adopta la nueva cita.

Los campos `ArcWidgetSlot` admitidos permiten elementos JSX individuales, texto o descriptores de widget. Pasa texto externo como texto JSX; los strings HTML se interpretan como HTML. [Combinar contenidos](/docs/react/patterns/nested-widget-descriptors).

## Tipos de datos

Estos tipos relacionados con el widget se exportan desde el paquete. También están disponibles tipos comunes como `ArcWidgetSlot`, `ArcWidgetStyles` y `ArcReactWidgetProps`. Las firmas de índice permiten extensiones; no garantizan que el widget evalúe campos arbitrarios.

```typescript
export type ArcWidgetCalendarWeekBorderLine = boolean | {
	color?: string;
	width?: string;
	borderColor?: string;
	borderWidth?: string;
	borderSize?: string;
	enabled?: boolean;
};

export interface ArcWidgetCalendarWeekBorders {
	color?: string;
	width?: string;
	outer?: ArcWidgetCalendarWeekBorderLine;
	columns?: ArcWidgetCalendarWeekBorderLine;
	rows?: ArcWidgetCalendarWeekBorderLine;
}

export interface ArcWidgetCalendarWeekStyles extends ArcWidgetStyles {
	borders?: ArcWidgetCalendarWeekBorders;
	headerBackgroundColor?: string;
	mutedColor?: string;
}

export interface ArcWidgetCalendarHighlight {
	date?: unknown;
	weekday?: number;
	color?: string;
	styles?: ArcWidgetCalendarWeekStyles | string;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarWeekDay {
	clickable?: boolean;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarDateSettings {
	startDate?: unknown;
	endDate?: unknown;
	duration?: number;
	highlights?: ArcWidgetCalendarHighlight[];
	day?: ArcWidgetCalendarWeekDay;
	header?: {
		days?: unknown;
		months?: unknown;
		weeks?: unknown;
		[key: string]: unknown;
	};
	month?: number;
	year?: number;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarTimeSettings {
	from?: number;
	to?: number;
	timeStart?: number;
	duration?: number;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarPlaceOffset {
	days?: number;
	time?: number;
}

export interface ArcWidgetCalendarPlaceSplit {
	title?: string;
	subtitle?: string;
	value?: string;
	styles?: ArcWidgetStyles | string;
	timeFrom?: unknown;
	timeTo?: unknown;
	offset?: number;
	duration?: number;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarPlaceSegment {
	duration?: number;
	linked?: boolean;
	offset?: ArcWidgetCalendarPlaceOffset;
	title?: string;
	subtitle?: string;
	value?: string;
	customLayout?: ArcWidgetSlot;
	styles?: ArcWidgetStyles | string;
	insetBorder?: unknown;
	dateFrom?: unknown;
	dateTo?: unknown;
	timeFrom?: unknown;
	timeTo?: unknown;
	splits?: ArcWidgetCalendarPlaceSplit[];
	[key: string]: unknown;
}

export interface ArcWidgetCalendarPlaceItem {
	active?: boolean;
	offset?: ArcWidgetCalendarPlaceOffset;
	splits?: ArcWidgetCalendarPlaceSplit[];
	duration?: number;
	title?: string;
	subtitle?: string;
	value?: string;
	customLayout?: ArcWidgetSlot;
	styles?: ArcWidgetStyles | string;
	insetBorder?: unknown;
	segments?: ArcWidgetCalendarPlaceSegment[];
	[key: string]: unknown;
}

export interface ArcWidgetCalendarCreateNew {
	item?: ArcWidgetCalendarPlaceItem | null;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarWeekDragStart {
	dateFrom?: unknown;
	dateTo?: unknown;
	timeFrom?: unknown;
	timeTo?: unknown;
}

export interface ArcWidgetCalendarTimeEntry {
	id?: string | number;
	title?: string;
	subtitle?: string;
	value?: string;
	customLayout?: ArcWidgetSlot;
	dateFrom?: unknown;
	dateTo?: unknown;
	timeFrom?: unknown;
	timeTo?: unknown;
	styles?: ArcWidgetStyles | string;
	clickable?: boolean;
	draggable?: boolean;
	segments?: ArcWidgetCalendarPlaceSegment[];
	tooltip?: {
		value?: ArcWidgetSlot;
		width?: string;
		maxWidth?: string;
		maxHeight?: string;
		position?: string;
		styles?: ArcWidgetStyles | string;
		[key: string]: unknown;
	};
	[key: string]: unknown;
}

export interface ArcWidgetCalendarWeekData extends Omit<ArcWidgetBaseData, 'actions'> {
	height?: string;
	styles?: ArcWidgetCalendarWeekStyles | string;
	weekdaySettings?: { week?: number; year?: number; [key: string]: unknown };
	weekStart?: unknown;
	dateSettings?: ArcWidgetCalendarDateSettings;
	timeZoneBalance?: number | string;
	timeSettings?: ArcWidgetCalendarTimeSettings;
	currentTime?: string;
	lang?: string;
	timeSlotWidth?: string;
	timeSlotHeight?: string;
	allDayHeight?: string;
	createNew?: ArcWidgetCalendarCreateNew;
	timeEntries?: ArcWidgetCalendarTimeEntry[];
}

export interface ArcWidgetCalendarWeekProps extends ArcReactWidgetProps<ArcWidgetCalendarWeekData> {
	onEntryClick?: (entry_index: number) => void;
	onDayClick?: (date_millis: number) => void;
	onEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarWeekDragStart) => void;
	onDailyEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarWeekDragStart) => void;
	onCreate?: (values: {
		dateFrom?: number;
		dateTo?: number;
		timeFrom?: number;
		timeTo?: number;
		[key: string]: unknown;
	}) => void;
}
```
