Skip to content

Planning Board

The planning board puts rows and time side by side: employees, rooms, machines or projects on the left, a time axis at the top, and entries as bars, points or cells in between. It shows who is present when, what is booked when, and where minimum staffing is not met. All data comes from SQL queries; every reaction to a click is a configured action.


Typical Use Cases

Use case Rows Entries
Absence planner Employees by team Vacation, sick leave, training, home office
Part-time and shift plan Employees Shifts, days off
Room, vehicle, machine booking Rooms, vehicles, machines Bookings, maintenance windows
Project and phase plan Projects Phases, milestones
Inspection deadlines Equipment, contracts Due dates as points

Common Stumbling Blocks

These five points cause the most confusion when setting up a board.

1. Entering means selecting days

A click on an empty day selects that day; dragging with the mouse button pressed selects several days of a row. Then a popover opens with the intents offered "on empty days (after selecting)". The popover shows the selected period like the card of an entry, with the last day as the end. While the popover is open, the next click on an empty day only closes it.

  • Selecting is only possible if at least one layer has such an intent.
  • On the week and month scale, whole weeks or months are selected.
  • Without selecting, entries are made with a button on the row or in the toolbar, see Entering an absence.
  • With the row source "Derive from the entries", a person without an entry in the selected period has no row – nothing can be selected for them. A rows query (for example from the sample schema "Employees") shows everybody.

2. "End" does not mean the same everywhere

Place "End" is
Layer with End column names the last day (inclusive) on the last day
Layer with this switch off the day after
Query for blocked and special periods, Markers from SQL always the last day
Static markers on the "Markers" tab (End (exclusive)) the day after
$(entry.end) and $(range.end) in questions, messages and URLs the day after
Prefill End (last day) or End (day after) as named

For absences with "from" and "to" date fields, this almost always fits: layer with "inclusive" on, prefill "End (last day)".

3. One faulty value hides the whole board

Instead of the board, a list of error messages appears. Typical causes:

  • The same record appears twice – for example because a JOIN duplicates rows or two layers read the same records.
  • A quantity is negative.
  • With timestamps, the end lies before or on the start.
  • Two layers or two markers have the same id.
  • A static period on the "Markers" tab does not end after its start. Add period first creates it exactly like that (start and end today) – so adjust the end right away.
  • "Shades of the base color" or "fixed layer color" is selected, but no base color is set.

4. "Hide" does not remove an entry

A hidden status (such as "Abgelehnt", rejected) or a hidden category is only not drawn. The entry still counts as absent and still triggers hints on minimum presence, overlaps and substitutes. In addition, every entry of a layer with a capacity effect above 0 counts as an absence regardless of its status – including "Beantragt" (requested).

Whatever should not count is therefore filtered out in the layer query, for example with AND COALESCE(status, '') NOT IN ('Abgelehnt', 'Storniert'), or goes into a layer of its own with capacity effect 0. Only a click in the legend also takes entries out of the count – and only for the person who clicked.

5. Entering needs a prepared process

The board itself never creates a record. New entries are only created through the step Start process: it needs a deployed process whose start form contains the prefilled fields under exactly these technical names and which saves the record the layer query reads. Only then does the entry appear on the board. The procedure step by step is described under Entering an absence.

Also important: the severity Blocking currently prevents nothing. A move is saved anyway, even if it violates a blocking rule and the hint in the dialog says otherwise.

More stumbling blocks

  • Row keys must match exactly, including upper and lower case. Entries whose key matches no row are not drawn. The safest way is to write the key with lower(...) in both the rows query and the layer query.
  • "Only in the user's own row" compares the row id exactly with the e-mail address the person signs in with.
  • Moving with the mouse needs Field for start (and for spans Field for end) in the layer's "Behavior" sub-tab, plus Record id and Data schema id in the column mapping. A separate "Move" action is not needed, even though the hint in the dialog mentions one. Entries are moved in time only, never to another row.
  • Saved view: The period and scale a person has chosen stay stored in their browser and take precedence over a default changed later.
  • Date comparisons in the queries: Never compare date columns with plain text; use CAST(@periodStart AS date) as in the examples. More under SQL in Widgets.

First Steps

The board needs at least one layer: an SQL query that returns one row per bar, with columns for the row (who or what), the start and the end. The rows of the board come either from a query of their own or are derived from the entries. After that, the dialog only needs to know which column is which.

Quickest way with the sample schemas:

  1. Add the "Planning board" widget to the dashboard and choose Configure planning board.
  2. Rows tab: Create sample schema “Employees”. In the dialog, check Schema name and Technical name (suggestion mitarbeiter; this is also the SQL table name) and choose Create. The rows query and column mapping are filled in.
  3. Layers tab: Create sample schema “Absences”, select the schema just created as Employees schema and choose Create. A layer with query and column mapping is created, together with the look of the categories (Krank red, Homeoffice outline only) and statuses (Beantragt hatched, Genehmigt solid, Abgelehnt hidden), the intent "Stornieren" (cancel) and moving with the mouse into the fields erster_tag and letzter_tag.
  4. Test run, Apply and publish the dashboard.
  5. Enter employees and absences in the database. The fields of the sample schemas are listed under Sample schemas.

Rejected absences are only hidden in this layer and still count as absent (see stumbling blocks). If they should not count, add AND COALESCE(a.status, '') <> 'Abgelehnt' to the layer query.

Existing data schemas for people or absences are connected with their own query and column mapping, see Rows and Layers.

Every tab of the dialog has an info button that opens the matching section of this page.


Use Case: Entering an Absence

Goal: in a person's row, the mouse is dragged over the desired days, a form opens with the person, first and last day already filled in, and after submitting, the absence appears as a bar.

What must exist first

  1. A data schema for the absences, in the example urlaubsantraege with the fields antragsteller (e-mail address of the person), von and bis (date, from and to), art (type, e.g. Urlaub, Krank), status and bemerkung (note).
  2. A deployed process, for example "Enter absence", with a start event that has a form. The start form contains fields with exactly the technical names antragsteller, von, bis and art, and the process saves a record into urlaubsantraege on submit. The board only offers start events of deployed processes.
  3. A layer that reads these records: kind "Span", temporal mode "Date", End column names the last day (inclusive) on, with this query:
SELECT id,
       data_schema_id,
       updated_at,
       lower(antragsteller) AS row_key,
       von AS start_day,
       COALESCE(bis, von) AS end_day,
       art AS category,
       COALESCE(status, 'Beantragt') AS status,
       bemerkung AS label
FROM urlaubsantraege
WHERE antragsteller IS NOT NULL AND antragsteller <> ''
  AND COALESCE(status, '') NOT IN ('Abgelehnt', 'Storniert')
  AND von < CAST(@periodEnd AS date)
  AND COALESCE(bis, von) >= CAST(@periodStart AS date)

Column mapping: Row (link) → row_key, Start → start_day, End → end_day, Category → category, Status → status, Label → label, Record id → id, Data schema id → data_schema_id, Updated at → updated_at.

With the row source "Derive from the entries", only people who already have an entry in the period appear. So that everybody has a row, the rows come from a rows query whose row id is also the lower-case e-mail address, for example lower(COALESCE(email, id)) AS id from the sample schema "Employees".

Setting up the intent

  1. Configure planning board → Layers tab → the layer → Actions sub-tab.
  2. Add intent.
  3. Label: "Enter absence"; Button: highlighted; Icon: event_available.
  4. Offered: "on empty days (after selecting)".
  5. Optionally, under Only when, the Condition (expert) range.workdays > 0 ? true : 'No working days selected' – if only weekends or holidays are selected, the button is then disabled with this reason.
  6. Under Then → Add step: Step "Start process", Process the start event of the absence process, Open as "Dialog". Prefilling only works in the dialog, not in a new tab.
  7. Under Prefill, choose Add prefill four times and fill it in as shown in the table below.
  8. Apply and publish the dashboard.
Target field Value from Fixed value
antragsteller Row (id, e.g. e-mail)
von Start
bis End (last day)
art Fixed value (with $(…) placeholders) Urlaub

The Target field is always the technical name of the field in the start form – not the name in the query (start_day) and not the field name in the data schema, if it differs.

Entering on the dashboard

  1. In the person's row, click an empty day or drag the mouse over the desired days.
  2. A popover shows the period (first to last selected day), the number of days and working days and the Enter absence button. The form receives the last day because the prefill uses "End (last day)".
  3. The button opens the start form with person, first and last day and type.
  4. Check and submit the form. As soon as the process has saved the record, the bar appears the next time the board loads. If the process saves the record only after an approval, the entry appears only then. How to make the entry visible at once is described under Visible right after sending.

Visible right after sending

Without further settings, the board shows a new entry only the next time it loads. Two settings on the "Start process" step (only with Open as "Dialog") make it visible at once:

  • Signal for reloading: name of a "throw signal" intermediate event in the process, right after the step that saves the record, e.g. gespeichert. When the signal arrives, the board reloads at once, and so do the other widgets of the dashboard. Without a signal it reloads when the run ends, after 30 seconds at the latest. Only with signal value: only a signal with this value counts; empty = any value. So that the entry is already there when the board reloads, the database step should save synchronously.
  • Show provisional bar: right after sending, a grey striped bar with the text "Saving …" appears in the row over the days from the form. It counts nowhere (staffing, notes, totals) and cannot be clicked or moved. As soon as the real entry is loaded, it takes the bar's place; at the latest with the reload after the signal or the end of the run, the grey bar is gone. While it is shown, the legend also lists "Saving …".

    • What the bar shows (only for buttons on an entry): "Show the new entry" as above, or "Hide the clicked entry" for "Cancel" or "Delete" through a process. Then the clicked entry turns into a grey striped bar with the text "Being removed …" right after sending. It counts nowhere any more and offers no actions. After the reload (signal or end of the run, after 30 seconds at the latest) it is gone, or back as before if the process kept the entry, for example because the cancellation was declined. Layer, start, end and row are not needed for this.
    • Text on the bar: your own text instead of "Saving …" or "Being removed …", e.g. "Requesting leave …" or "Cancelling …".
    • Layer of the entry (only for buttons on the row and in the toolbar): the layer on which the real entry appears; "First bar layer" takes the first layer with bars. For intents on a layer it is always that layer.
    • Field for start, Field for end, Field for the row: technical names in the start form. Empty = the fields from the prefill ("Start", "End (last day)", "Row (id, e.g. e-mail)"); they are shown in grey in the empty field. Without a field for the row, the bar appears in the clicked row, otherwise in the row of the signed-in user.

    The bar shows the days sent in the form, even if the user changed them there. Without a start in the form and without selected days, no bar appears.

Entering single days

A click on an empty day selects exactly that day; the popover offers the same buttons as after dragging. Without selecting, two more intents with the same "Start process" step do it:

  • Button on the row: Rows tab → What can be done with a row → Add intent, label "Enter absence for this person", prefill antragsteller ← "Row (id, e.g. e-mail)". A click on the person's name opens the row card with this button; first and last day are entered in the form.
  • Button in the toolbar: Display tab → Buttons in the toolbar → Add intent, label "Request absence", prefill antragsteller ← "Signed-in user (e-mail)". This also works for people who do not have a row yet.

Optional: cancelling and moving

  • Cancelling: intent "Cancel", Offered "on an entry (card and right-click)", Statuses (comma list) Beantragt, Genehmigt, Only in the user's own row on, question "Really cancel the absence starting $(entry.start)?", step Set field with Field name status and Value Storniert. Because the query filters out cancelled entries, the bar disappears the next time the board loads.
  • Cancelling through a process: if the cancellation can be checked or declined, use the step Start process with the cancellation process instead of Set field. With Show provisional bar, What the bar shows "Hide the clicked entry" and the text "Cancelling …", the bar turns grey at once and no longer counts. If the process declines, it is back as before after the reload.
  • Moving: layer's Behavior sub-tab, Move and Resize on, Field for start von, Field for end bis, Locked for status (comma list) Genehmigt. Moving writes von and bis directly into the record, without the process.

Building Blocks

  • Rows: an SQL query with row id, display name, optionally subtitle, group, subgroup, sort order, capacity, workdays and state. Rows appear even without entries.
  • Layers: one query of entries per layer. Each layer defines whether it contains spans or points, dates or timestamps, whether the end column means the last day, and what an empty end means. If a setting is missing, the board shows the error instead of a guessed picture.
  • Availability: working days per row (a column such as 1,2,3,4,5), public holidays from the account's work calendar or per state, optionally a query for blocked and special periods.
  • Style rules: field plus value gives a color, a pattern (solid, hatched, dashed, dotted, striped, outline), a code for the cells view, or hiding. Example: status "Beantragt" hatched, "Abgelehnt" hidden.
  • Groups: frames around rows with "present/total" per day or as a heatmap.
  • Result columns: values from the rows query to the right of the board, for example vacation taken, remaining vacation as a progress bar, status as a badge. The database computes the sums; the board displays them.
  • Rules: minimum presence per group (people, percent or full-time equivalents), overlaps, absent substitutes, plus own hints from SQL. Every hint has a severity and a sentence.
  • Markers: dates and periods without a row, such as a trade fair, a release or company holidays.
  • Intents: what the user can do, in their words ("Request vacation", "Cancel"), configured on the layer, the rows or the board; behind them, steps such as starting a process with a prefilled form, setting a field, deleting a record, publishing on a channel or opening a URL. A click on an entry opens a card with facts and the matching intents.

Configuration

The dialog has seven tabs: Rows, Layers, Display, Groups & rules, Result columns, Style rules and Markers. Inside, each layer has the sub-tabs Data, Look, Behavior and Actions. For all queries: Test run runs the query with five rows and shows the columns found; the mapping fields then offer these columns. The placeholders work as in all SQL widgets.

Rows

Source of the rows: SQL query (default; one row per employee, room, machine) or Derive from the entries (every row key of the layers becomes a row, without groups and capacity).

Column mapping of the rows query (in brackets the technical key as it appears in the saved configuration):

Mapping Required Meaning
Row id (id) yes Row key. The layers refer to exactly this value with their "Row (link)" column, for example the user's e-mail.
Display name (label) yes Name in the name column
Subtitle (sublabel) such as role or e-mail
Group (id), Group (name) (groupId, groupLabel) Group (team, department, building)
Subgroup (id), Subgroup (name) (subgroupId, subgroupLabel) Subgroup
Sort order (sortOrder) Sort order within the group
Capacity (FTE) (capacity) Job share 0 to 1 for presence in full-time equivalents
Workdays (1,2,4 or Mo,Tu; 0 or 7 = Sunday; empty = workdays of the board) (workdays) Workdays as numbers 1,2,4 (1 = Monday … 6 = Saturday, 0 or 7 = Sunday) or day names monday,tuesday, Mo,Di, as a comma list or array; other days turn grey. Empty = the board's workdays from the "Groups & rules" tab
State (holidays per row, e.g. BY) (region) German state code (BW, BY, …) for the public holidays of this row if it differs from the account. Empty = the board's "Holidays" setting

The switch Show initials as avatar (default on) puts a circle with the row's initials in front of the name.

Further columns of the query are available as result columns.

Insert example offers the query for the employees from the sample schema (table mitarbeiter):

SELECT COALESCE(email, id) AS id,
       name AS label,
       email AS sublabel,
       team AS group_id,
       team AS group_label,
       array_join(arbeitstage, ',') AS workdays,
       bundesland AS region,
       fte AS capacity
FROM mitarbeiter
WHERE COALESCE(aktiv, true) = true
ORDER BY team, name

Mapping: Row id → id, Display name → label, Subtitle → sublabel, Group (id) → group_id, Group (name) → group_label, Workdays → workdays, State → region, Capacity (FTE) → capacity.

Availability on the same tab: the Holidays setting defines where days off come from – Work calendar of the account (public and regional holidays plus company days off from Administration → System → Work calendar; half days as half availability; default for new boards), State of the board (built-in rules) (only the public holidays of the state selected in the widget; boards created before the work calendar keep this setting) or No holidays. Rows with their own value in State get the public holidays of their state if it differs. Holidays are computed by rules (fixed days plus the Easter formula, rules per state with validity years), without maintaining any data. In addition, an optional availability query for blocked and special periods with the mapping Row (empty = all rows), Start, End (last day), State (unavailable, partial, available), Capacity (0 to 1 for partial) and Label. The button Example: company holidays inserts this query for all rows:

SELECT CAST(NULL AS varchar) AS row_key,
       CAST(concat(CAST(year(current_date) AS varchar), '-12-24') AS date) AS start_day,
       CAST(concat(CAST(year(current_date) AS varchar), '-12-31') AS date) AS end_day,
       'unavailable' AS state,
       'Betriebsferien' AS label

Layers

Each layer has four required settings that leave nothing to guesswork:

Setting Values Meaning
Kind Span, Point Bar with start and end, or a point
Temporal mode Date, Timestamp Date as YYYY-MM-DD or a timestamp with time
End column names the last day (inclusive) on, off Only for span + date: the end column names the last day (on) or the day after (off)
Empty end means Error (not allowed), runs until now, runs to the edge Only for spans: what applies when the end column is empty

In addition Color from: "Shades of the base color" (default: every category from the data automatically gets a stable shade), "Category (palette)" (every category its own strong color), "fixed layer color" or "style rules only". The Base color is chosen from named colors: None (default), Blue, Teal, Green, Orange, Red, Violet, Pink, Slate or "Custom color…". Below that two tables: Categories sets individual values (Krank red, Homeoffice outline only), Statuses sets the pattern per status value (Beantragt dotted, Genehmigt solid, Abgelehnt hidden). A layer created with Add layer starts with Beantragt dotted, the example "Sample schema Absences" sets Beantragt hatched. Everything not listed there stays automatic; with "Distinguish unknown statuses automatically", unlisted statuses also get a fixed pattern based on their name. Order: base color → category → status → style rules; each step changes only what it sets. Also here: the Capacity effect (0–1) (1 = the entry makes the row absent, 0 = no effect, for example working time) and the Unit of the amount.

Column mapping of the layer query (in brackets the technical key):

Mapping Required Meaning
Row (link) (row) yes Row key, must match the row id of the rows
Start (start) yes Date or timestamp
End (end) for spans Date or timestamp; with "End column names the last day (inclusive)" the last day
Day part (am/pm/full) (dayPart) am, pm or full for half days (span + date only)
Amount (quantity) Amount in the layer's unit, for example days or hours counted
Category (category) Category for color and legend
Status (status) Status for style rules, locks and conditions
Label (label) Text on the bar
Linked row (substitute) (linkRow) Row key of the substitute; the rule "Check substitutes" warns if they are absent themselves. If the substitute is not on the board (for example from another team), the card still shows them, but they are not checked
Record id, Data schema id (id, dataSchemaId) for actions so that "Open record", "Set field" and moving work
Updated at (updatedAt) Time of change, used to match the data after writes

Below the mapping is the field Additional columns on the card: a comma list of technical column names of the query whose values appear in the tooltip and on the card of the entry.

Timestamps arrive as ISO text with a time zone (2026-10-09T08:00:00+02:00) or as an Athena timestamp, which is read as UTC.

Insert example offers two queries. Absences from the sample schema (span, date, "End column names the last day (inclusive)" on; tables abwesenheiten and mitarbeiter; the reference fields contain the id of the employee record):

SELECT a.id,
       a.data_schema_id,
       a.updated_at,
       COALESCE(m.email, m.id) AS row_key,
       a.erster_tag AS start_day,
       a.letzter_tag AS end_day,
       CASE WHEN a.erster_tag = a.letzter_tag AND a.erster_tag_vollhalb = 'Halb' THEN 'am' ELSE 'full' END AS day_part,
       a.kategorie AS category,
       a.status AS status,
       a.tage AS quantity,
       a.bemerkung AS label,
       COALESCE(v.email, v.id) AS link_row
FROM abwesenheiten a
LEFT JOIN mitarbeiter m ON m.id = a.mitarbeiter
LEFT JOIN mitarbeiter v ON v.id = a.vertretung
WHERE a.erster_tag < CAST(@periodEnd AS date)
  AND a.letzter_tag >= CAST(@periodStart AS date)
  AND COALESCE(a.status, '') <> 'Storniert'

Mapping: Row (link) → row_key, Start → start_day, End → end_day, Day part → day_part, Category → category, Status → status, Amount → quantity, Label → label, Linked row (substitute) → link_row, Record id → id, Data schema id → data_schema_id, Updated at → updated_at. The example also fills the layer's Categories table (Krank red, Homeoffice outline only) and Statuses table (Beantragt hatched, Genehmigt solid, Abgelehnt hidden), adds the intent "Stornieren" (cancel) and, on the "Behavior" sub-tab, moving and resizing into the fields erster_tag and letzter_tag, locked for "Genehmigt" and "Storniert". The values of the sample schema are German: Beantragt (requested), Genehmigt (approved), Abgelehnt (rejected), Storniert (cancelled), Krank (sick).

Time entries from the "Time tracking" app (span, timestamp, capacity effect 0, unit hours). Date and time are combined into a timestamp in Europe/Berlin:

SELECT id,
       data_schema_id,
       updated_at,
       app_mitarbeiter AS row_key,
       to_iso8601(with_timezone(date_parse(concat(CAST(app_datum AS varchar), ' ', app_arbeitsbeginn), '%Y-%m-%d %H:%i'), 'Europe/Berlin')) AS start_at,
       to_iso8601(with_timezone(date_parse(concat(CAST(app_datum AS varchar), ' ', app_arbeitsende), '%Y-%m-%d %H:%i'), 'Europe/Berlin')) AS end_at,
       app_kategorie AS category,
       app_arbeitsstunden AS quantity,
       app_bemerkung AS label
FROM app_time_entries
WHERE app_datum < CAST(@periodEnd AS date)
  AND app_datum >= CAST(@periodStart AS date)

Here date_parse is correct: working start and end are stored as text (08:00) and are combined with the date into a text first. date_parse never belongs on a date field itself, see SQL in Widgets.

The board's period filter loads only the visible range, so the condition with @periodStart and @periodEnd belongs in every layer query.

Display

The dialog shows theme, scales and periods with their technical values, not translated: theme light, soft, midnight, contrast, pantarey or auto (following the system setting); scales hour, day, week, month; periods today … all.

Setting Values (default)
Display Bars | Cells (day scale); the cells view shows a code per day on the day scale
Density compact | comfortable (default) | spacious
Theme light, soft, midnight, contrast, pantarey, auto
Legend bottom (default) | top | none
Hints below the board none | compact (default) | all; a click on a hint jumps to the day and the row
Default scale one of the scales
Scales (chips) default day, week, month; hour = hour scale, only available via chip
Sub-rows per row 1–6 (default 3)
Width of the name column (px) 120–480 (empty = 240)
Show period filter switch
Default period default quarter
Offered periods (chips) default month, quarter, year
Calendar selection, Month selection, Year selection switches
Week numbers, Now line, Minimap, Totals row, Headcount present/total switches, all on
Full-time equivalents, Zebra rows switches, off
Search field, Context menu (right-click) switches, on
Snap for timestamps (minutes) 1–240 (default 15)
Buttons in the toolbar intents for the whole board, see Actions

Moving and resizing with the mouse are not set here but per layer on the Behavior sub-tab under "Change period with the mouse": Move, Resize, Field for start, Field for end and Locked for status (comma list).

Groups and Rules

Groups come from the rows query. Frame rows by group (on) draws the frame around the rows of a group; Group row per day shows nothing, present/total (default) or a heatmap; Order of the groups is as in the data or alphabetical. Workdays of the board: tick Mo–Su, default Mo–Fr.

Rules: Check minimum presence (off) with Value (default 2), Unit (People | Percent | FTE), Severity (Info | Warning | Blocking) and Scope (per group | whole board); Check overlaps (on, Info) within a row, with the chips Only layers optionally only for certain layers; Check substitutes (on, Warning) reports a substitute who is absent themselves. "Blocking" currently only highlights and does not prevent a move.

Own hints from SQL

Besides the built-in rules, the board can show hints from a query of your own, for example days with more than 10 hours or missing breaks. Every row of the result becomes a hint: in the counter at the top and in the list under the board, with Jump to the date.

This is set in the Groups & rules tab under Own hints from SQL: enter the query, Test run, then map the columns.

Column Required Meaning
Message yes text of the hint
Severity info, warning or blocking (also Hinweis, Warnung, Blockierend); otherwise warning
Row row key as in the rows query; hints for rows that are not on the board are left out. With row and start, the day is also colored in the group row
Start first day (date or timestamp)
End last day; empty = only the start day
Entry id of an entry (as in the layer's Id column): its bar gets the hint's color, the text is in the tooltip
Id optional, a fixed key of the hint

Example with the time entries of the "Time tracking" app: one hint per person and day with more than 10 hours. Mapping: Message message, Severity severity, Row row_key, Start day.

SELECT app_mitarbeiter AS row_key,
       app_datum AS day,
       'More than 10 hours worked: ' || CAST(ROUND(SUM(app_arbeitsstunden), 1) AS varchar) || ' h' AS message,
       'warning' AS severity
FROM app_time_entries
WHERE app_datum < CAST(@periodEnd AS date)
  AND app_datum >= CAST(@periodStart AS date)
GROUP BY app_mitarbeiter, app_datum
HAVING SUM(app_arbeitsstunden) > 10

The message appears in the list exactly as the query delivers it. To include the person's name, fetch it in the query, for example with a JOIN on the table of the rows. Like all queries of the board, this one runs with the rights of the person viewing the dashboard.

Result Columns

Per column: Id, Header, Column of the rows query (required), Maximum (for progress) as a further column, Format (Number | Progress | Badge | Text), Unit, Decimals (0–3, default 1), Totals (none | sum rows), Width (px) (60–300, default 110). Values such as "vacation taken this year" are computed by the rows query, for example with SUM(...) FILTER (WHERE ...) and a LEFT JOIN on the absences.

Style Rules

An expert tool for everything that cannot be set on the layer, for example "all entries with note X hatched". Rules apply after the layer's base color, category and status, in order; later ones override earlier ones. The field is category, status, layerId, label or a column of the query; the value a comma list or *. Effect: color, pattern, code for the cells view, opacity, hide. Optionally only for certain layers.

Markers

Points in time and periods with label and color, drawn across the whole board without a row. Static entries are created in the dialog with Add point in time (field Date) or Add period (fields Start and End (exclusive)), each with a Color; in addition, an optional query Markers from SQL with the columns start (date, required), end (last day; filled gives a period, empty a point), label, color and id, for example from a data schema "Dates". Both sources are drawn together.

Actions

What a user can do is called an intent on the board and is configured where it belongs:

  • On the layer (the layer's "Actions" sub-tab): intents on an entry ("Cancel", "Approve") and on empty days ("Request vacation" after selecting empty cells).
  • With the rows: intents when clicking a name ("Show remaining vacation").
  • In the display: buttons for the whole board in the toolbar.

Every intent has a label in the user's words, an icon, a Button (neutral, highlighted, green (confirming), red (attention)), conditions (only for certain statuses, only in the user's own row, only for user groups, optionally a JSONata condition), optionally a question with its own title, text (default "Really execute?") and buttons (each button proceeds or cancels; its value is available to the steps as $(choice)), a success message (default "Done.") with Display as a short message at the bottom or a dialog with OK, and a list of steps that run one after another:

Step Effect
Start process opens the start form of a start event of a deployed process in a dialog, with Prefill: target field ← Row (id, e.g. e-mail), Row (display name), Start, End (last day), End (day after), Record id, Category, Status, Signed-in user (e-mail) or fixed value; optionally Signal for reloading and Show provisional bar (Visible right after sending)
Set field writes a fixed value into a field of the record, such as status "Storniert"
Open record Card with facts and intents or Record in the database (only for the latter: In new tab); users who do not have the permission to open the database get the card instead, and the card leaves out an intent that only opens the database
Delete record after a confirmation, cannot be undone
Publish on channel sends the entry or the selection to other widgets of the dashboard
Open URL opens a URL with placeholders such as $(entry.id), $(row.id), $(range.start)
Refresh reloads all widgets of the dashboard, not only the board

If a step aborts (cancel in the form, error), the following steps do not run. Moving and resizing with the mouse are not intents; they are set on the layer's "Behavior" sub-tab with the fields written to and the locked statuses.

The card. A click on an entry always opens the same card: the facts in plain words at the top (row, layer, period, category, status, amount, substitute, additional columns), below them the intents that currently apply to this entry as buttons, and the link "Open in the database". Only users with the permission to open the database see the link; whether they may read the record there is decided by the permissions. A right-click shows the same intents as a menu, plus the entry Details, which opens the card. A click on a name opens the row card with group and result columns. If no intent currently applies to an entry, the card shows the facts and below them "No action is configured for this entry."

A new board has no intents. The card still always opens on a click on an entry; a separate "Open" action is not needed for that.


Placeholders

In all queries of the board:

Placeholder Becomes
@periodStart, @periodEnd Limits of the selected period as a timestamp expression; compare with date columns using CAST(@periodEnd AS date). @periodEnd is the first day after the period.
@currentUserEmail, @currentUserSub the signed-in user, for example for "only my row"
@currentDate, @currentYear, @currentMonth, @currentTimeStamp today's date, year, month, point in time
$(parameter) value from the surrounding filter container

Quotes, date comparisons and more examples for all placeholders are described under SQL in Widgets.


Sample Schemas

The two "Create sample schema" dialogs create normal data schemas that can be extended freely in the Data Designer. The technical name is also the table name in SQL; the suggested names and field names are German.

Employees (suggested name mitarbeiter):

Field Type Use on the board
email Pantarey user Row key; the "Absences" layer points to this row through the reference mitarbeiter
name Text, required Display name
team Text Group
arbeitstage Multiple selection monday … sunday working days; tick only for fixed part-time days; empty = the board's working days. In SQL, array_join(arbeitstage, ',') turns it into a list
fte Number, default 1 Job share for full-time equivalents
bundesland Selection state: public holidays of this row if they differ from the account; empty = the board's "Holidays" setting
aktiv Yes/No, default yes active: hide employees who have left

Absences (suggested name abwesenheiten):

Field Type Use on the board
mitarbeiter Reference to Employees, required Row
erster_tag, letzter_tag Date, required first and last day: start and end (inclusive)
erster_tag_vollhalb, letzter_tag_vollhalb Voll/Halb (full/half) Half days
kategorie Jahresurlaub, Krank, Fortbildung, Homeoffice category: color and legend
status Beantragt, Genehmigt, Abgelehnt, Storniert Styles, locks, conditions
tage Number days: quantity
vertretung Reference to Employees substitute: substitute check
bemerkung Text note: text on the bar

Reference fields store the id of the linked record. In SQL, the join is therefore made with m.id = a.mitarbeiter.


Usage

  • Switch the scale at the top right; Today jumps to the now line; the minimap below the toolbar shows the loaded period and jumps on click. The period picker is missing from the toolbar when a filter container sets the period.
  • States: as long as nothing is configured, the widget says "Planning board not configured"; while loading "Board is loading …"; if a query fails, "Error loading" with the error message.
  • Tooltip with period, status, amount and hints when hovering over a bar.
  • Legend at the bottom, grouped per layer: the layer, its categories from the data, its status patterns. A click hides the layer, a category or a status. The browser remembers the choice. While something is hidden, the toolbar shows for example "2 hidden · Show all"; hovering over it names what is hidden, a click shows everything again. With Legend: none, nothing is hidden, not even a hiding saved earlier in the browser.
  • Hints: the counter in the toolbar and the button "Show all … hints" under the board open a list of all hints, blocking first, then warning, then info, each by date. Chips filter by severity, the search field by name, text or date. A click on a hint closes the list and jumps to the day and the row of the person.
  • Expand and collapse groups; the group row counts per day, and days below minimum presence are colored.
  • Dragging: move bars, extend them at the edges. While dragging, a hint shows how the team's presence changes on the most critical day. Locked statuses refuse with a reason.
  • Selection: click an empty cell or drag over empty cells, then the configured intents appear with the number of days and working days; half days off count as 0.5. Another click closes the popover.
  • Search (field "Search…" in the toolbar) filters rows and entries; Escape clears the selection; Tab moves through the bars.

Notes

  • The board never changes data by itself. When moving or with "Set field", the widget writes the configured fields and shows the entry striped until the database returns the new state.
  • The selected period is loaded. Result columns such as "vacation taken this year" therefore belong in the rows query, not in the layer.
  • The query decides who sees which rows: WHERE m.email = '@currentUserEmail' shows only the user's own row, a condition on the team only the colleagues.
  • Entries whose row key matches no row are not drawn. In the designer, the preview shows how many.