---
title: "Custom Calendar Grid"
slug: "custom-calendar-grid"
category: "Widgets"
reactComponent: "ArcWidgetCalendarGrid"
reactTier: "premium"
reactSince: "0.1.0-alpha.5"
reactExample: "calendar_grid.tsx"
locale: "es"
hosts: "ninox"
---

# Custom Calendar Grid

## AI Defaults (read first)

- **uniqueId**: Required, `"cal-grid-" + Nr`.
- **height**: Use `"100%"` when embedded in a layout.
- **timeZoneBalance**: Set `-1` for German timezone.
- **dateSettings.month/year**: 1–12 für Monat, Jahr für die Monatsansicht.
- **dateFrom/dateTo**: Must be actual date field values in timeEntries.
- **dragAction**: Requires `recordId` and field IDs via `fieldId(Nr, "Feldname")`.
- **createNew**: Needs `tableId`, field IDs for target fields.
- **Persistence**: Drag saves go through the **Action Performer** (`type: "update"`). Ninox `dragAction` config is unchanged. React hosts: handle via `onAction` — **no** global `database` stub.
- **day.actions**: Array of actions with `##clickedDate##` placeholder – use instead of clickAction.

# Custom Calendar Grid

Con el widget **Custom Calendar Grid** llevas una vista de calendario en cuadrícula mensual a tu base de datos Ninox. Muestra un mes de calendario clásico con filas de semanas y celdas de día — ideal para vistas mensuales, planificación de vacaciones o vista de citas.

💡 **Lo que puedes hacer con Custom Calendar Grid:**

- **Vista mensual:** Cuadrícula clásica de 6 semanas con días de la semana y semanas calendario.
- **Entradas con hora y de todo el día:** Muestra citas con hora y eventos de varios días.
- **Arrastrar y soltar:** Mueve citas directamente en el calendario.
- **Clic en un día:** Ejecuta acciones con la fecha clicada como marcador.
- **Resaltado:** Marca fines de semana, festivos o días especiales.
- **Estilo individual:** Adapta cabecera, días de la semana y celdas de día.

## Código de aplicación

```javascript
let current := this;
arcCustomCalendarGrid({
    uniqueId: "cal-grid-" + Nr,
    embedded: false,
    height: "100%",
    timeZoneBalance: -1,
    lang: clientLang(),
    dateSettings: {
        month: month(today()),
        year: year(today()),
        header: {
            title: { label: "##monthLong## ##year##", fontSize: "18px", fontColor: "#333" }
        },
        weekdays: { label: "##weekDayShort##", fontSize: "13px" },
        week: { show: true },
        day: {
            height: "120px",
            alignX: "left",
            actions: [{
                type: "update",
                recordId: Nr,
                fieldId: fieldId(Nr, "helper_ui_state_backoffice"),
                dataBag: {
                    base: helper_ui_state_backoffice,
                    patch: { cal_view: "1", cal_date: "##clickedDate##" }
                }
            }]
        },
        highlights: [
            { weekday: 6, day: { backgroundColor: "#fafafa" } },
            { weekday: 0, day: { backgroundColor: "#fafafa" } }
        ]
    },
    timeEntries: (select Termine where month(Termin) = month(today()) and year(Termin) = year(today())).[{
        title: Bezeichnung,
        dateFrom: Termin,
        dateTo: Termin,
        timeFrom: von,
        timeTo: bis,
        styles: { backgroundColor: "#3388FF", color: "#fff", borderRadius: "6px" },
        dragAction: { recordId: Nr, dateFrom: "A", timeFrom: "B", dateTo: "C", timeTo: "D" },
        clickAction: { type: "popup", recordId: Nr }
    }]
})
```

## dateSettings – periodo y representación

### month / year

Define el mes a mostrar. `month` es 1–12 (enero = 1), `year` es el año.

```javascript
dateSettings: {
    month: 11,
    year: 2024
}
```

### startDate / endDate / duration (opcional)

Como alternativa a `month`/`year` puedes definir un periodo explícito:

- **startDate**: Fecha de inicio (milisegundos o fecha de Ninox)
- **endDate**: *(opcional)* Fecha de fin. Tiene prioridad sobre `duration`.
- **duration**: *(opcional)* Número de días a mostrar. Solo se tiene en cuenta si `endDate` no está configurado.

```javascript
dateSettings: {
    startDate: date(2024, 11, 1),
    endDate: date(2024, 11, 30)
}
```

O con `duration` en lugar de `endDate`:

```javascript
dateSettings: {
    startDate: date(2024, 11, 1),
    duration: 42
}
```

### day – celdas de día y acciones de clic

Bajo `dateSettings.day` controlas el aspecto de las celdas de día y las acciones al hacer clic en un día.

#### day.actions – acciones al hacer clic en un día

Con `day.actions` defines un **array de acciones** que se ejecutan al hacer clic en una celda de día. El marcador `##clickedDate##` se sustituye por la **marca de tiempo en milisegundos** de la fecha clicada.

```javascript
day: {
    actions: [{
        type: "update",
        recordId: Nr,
        fieldId: fieldId(Nr, "helper_ui_state_backoffice"),
        dataBag: {
            base: helper_ui_state_backoffice,
            patch: { cal_view: "1", cal_date: "##clickedDate##" }
        }
    }]
}
```

**Marcadores:**

| Platzhalter | Ersetzt durch |
|-------------|---------------|
| `##clickedDate##` | Millisekunden-Timestamp des geklickten Datums (z. B. `1731369600000`) |

**Tipos de acción:** Igual que en `arcCustomInput` y otros widgets – `update`, `popup`, `openUrl`, etc. Consulta `action_performer` para todos los tipos admitidos.

**Importante:** El marcador se sustituye de forma recursiva en todos los valores string de la acción — también en objetos anidados (p. ej. en `value` en actualizaciones de estado JSON).

#### Otras propiedades de day

- `height`: Altura mínima de la celda de día (p. ej. `"120px"`)
- `alignX`: `"left"`, `"center"`, `"right"` para la alineación del número de día
- `backgroundColor`, `hoverBackgroundColor`: Colores de fondo
- `padding`: Espaciado interno
- `number`: Estilo del número de día (fontSize, fontColor, containerSize, etc.)
- `maxTimeEntries`: Limita las entradas visibles por día (ver abajo)

#### maxTimeEntries – límite de entradas visibles por día

Si un día tiene más entradas que `amount`, solo se muestran las primeras. Aparece un aviso «+ X más»; al hacer clic se ejecuta la misma acción que al hacer clic en la celda de día (`day.actions`).

```javascript
maxTimeEntries: {
    amount: 5,
    suffix: "weitere"
}
```

| Eigenschaft | Bedeutung |
|-------------|-----------|
| `amount` | Maximale Anzahl sichtbarer Einträge. Ab dem (amount+1). Eintrag wird gekürzt. |
| `suffix` | Text für „+ X suffix“ (z. B. `"weitere"` → „+ 3 weitere“). Standard: `"more"`. |

### week – número de semana calendario

Con `week.show` muestras la semana calendario ISO en el primer día de cada semana:

```javascript
week: {
    show: true,
    number: {
        fontSize: "12px",
        fontColor: "#999",
        fontWeight: "normal"
    }
}
```

### header – cabecera de mes/año

- `backgroundColor`: Color de fondo
- `title`: `label`, `fontSize`, `fontColor`, `fontWeight`, `alignX`
- `subtitle`: *(opcional)* Segunda línea con `label`, `fontSize`, `fontColor`, `fontWeight`, `alignX`

Marcadores para `label`: `##monthLong##`, `##year##`, `##weekDayShort##` etc. Consulta `custom-calendar-week` para todos los marcadores.

### weekdays – fila de días de la semana

- `label`: Marcador (p. ej. `"##weekDayShort##"`)
- `backgroundColor`, `fontSize`, `fontColor`, `fontWeight`, `padding`, `alignX`

### highlights – resaltado de días

Resaltado por día de la semana o fecha concreta:

```javascript
highlights: [
    { weekday: 6, day: { backgroundColor: "#fafafa" } },
    { weekday: 0, day: { backgroundColor: "#fafafa" } },
    { date: 1731369600000, day: { number: { containerSize: "32px", backgroundColor: "#4970ff", borderRadius: "50%", fontColor: "#fff" } } }
]
```

- `weekday`: 0–6 (domingo = 0, sábado = 6)
- `date`: Marca de tiempo en milisegundos para una fecha concreta
- `day`: Estilo para la celda de día (backgroundColor, number, padding, etc.)

## styles – estilo general del calendario

Mediante `styles` puedes diseñar el contenedor **más externo** del calendario. Todos los ajustes (p. ej. `borderRadius`, `border`, `borderColor`, `backgroundColor`) se aplican directamente al contenedor más externo. El radio de borde por defecto es `8px`. Si se establece `borderRadius`, el contenedor recibe automáticamente `overflow: hidden`, para que las esquinas redondeadas se representen limpiamente.

```javascript
styles: {
    borderRadius: "8px",
    borderColor: "#ddd",
    backgroundColor: "#fff"
}
```

## timeEntries – entradas de calendario

Cada entrada necesita:

- `dateFrom`, `dateTo`: Fecha (milisegundos)
- `timeFrom`, `timeTo`: Opcional – hora en milisegundos (0 = medianoche). Sin hora = entrada de todo el día
- `title`, `subtitle`, `value`: Visualización (o `customLayout` para estructura HTML propia)
- `styles`: Estilos similares a CSS (backgroundColor, color, borderRadius, padding)
- `dragAction`: Para arrastrar y soltar; `recordId`, `dateFrom`, `timeFrom`, `dateTo`, `timeTo` como Field IDs. Persistencia mediante Action Performer (`type: "update"`); React: `onAction`, sin `database` global.
- `clickAction`: Para el clic en la entrada (p. ej. `{ type: "popup", recordId: Nr }`)

**customLayout:** String HTML para representación individual en lugar de `title`/`subtitle`/`value`. En entradas de todo el día, el marcador `##entrytime##` sustituye el intervalo de tiempo (p. ej. «00:00 - 23:59» en eventos de un solo día).

## createNew – crear nuevas entradas

Define cómo se crean nuevas entradas (p. ej. al hacer clic en un día vacío):

```javascript
createNew: {
    tableId: "B",
    dateFrom: "A",
    timeFrom: "B",
    dateTo: "C",
    timeTo: "D"
}
```
