Translate

Donnerstag, 2. Oktober 2025

APEX Calendar - vertikale Schrift mit Feiertagen

 Kürzlich habe ich (mit Hilfe ChatGPT) dieses hier realisiert:

Die Feiertage (auf meiner P12) tauchen im Kalender als vertikale Einträge auf.

Sieht so easy aus.... aber ist nicht trivial... aber machbar... wie folgt:

Mein SQL für den Kalender sieht so aus für Platz 1 (Region):
(PS: für 4 Plätze habe ich 4 Regionen untereinander oder nebeneinander...)

select ID,
    case 
    when partner is null then name||'<br>'||Gastspieler
    else name||'<br>'||partner
    end anzeige,
       TAG,
       TAG_BIS,
       PLATZ,
    case 
    when type='Manschaftstraining' then 'apex-cal-cal-green' --'apex-cal-green' 
    when type ='Buchung' and BALLMASCHINE = 'Y'  then 'apex-cal-red' 
    when type ='Buchung' and GASTSPIELGEBUEHR = 10  then 'apex-cal-gray'
    when type='Buchung'      then 'apex-cal-cyan' -- cyan ist gut hellgrün
    when type='Punktspiel'   then 'apex-cal-orange'
    when type='Training'     then 'apex-cal-lime'
    when type='gesperrt'     then 'apex-cal-black' 
    when type='Feiertag'     then 'apex-cal-yellow'
  end as css_farbe,
  BEMERKUNGEN
  from TENNIS_BOOKING_TAG
where PLATZ = 1

Nun zu den "Feiertagen":

1. Dazu benötige ich zunächst eine Tabelle mit den Feiertagen..."vc_holidays_de" (ID, Name, Datum)

2. dann einen Before_Header pl/sql Process "get_holidays_de" der füllt :P12_Holidays_json mit dem Namen des Feiertags:

declare

  l_json clob := '[';
  l_cnt  integer := 0;
begin
  for rec in (
    select to_char(holiday_date, 'YYYY-MM-DD') as holiday_date,
           name
      from vc_holidays_de
     where holiday_date between trunc(sysdate, 'YYYY') and trunc(sysdate + 365)
  )
  loop
    if l_cnt > 0 then
      l_json := l_json || ',';
    end if;
    l_json := l_json || json_object(
      'date' value rec.holiday_date,
      'name' value rec.name
    );
    l_cnt := l_cnt + 1;
  end loop;
  l_json := l_json || ']';
  :P12_HOLIDAYS_JSON := l_json;
end;


3. Platz 1 - Region --> Attributes --> JS initialisation options :

ein Javascript auf P12 fügt den Namen ein (siehe im 2. Screenshot ganz unten):

JS Code:

function(pOptions) {

  return getCalendarOptions("PLATZ1", "P12_HOLIDAYS_JSON", pOptions);

}














das ist aber noch nicht alles.... jetzt zum CSS... und der vertikalen Darstellung:

Das folgende CSS rufe ich durch 
#WORKSPACE_FILES#holidays.css auf der Seite 12 auf bei den Page Attributes unter CCS, files/URL auf 

und habe es vorher als Workspace_file bei den Shared Components gespeichert.
Es sieht so aus:

.fc .fc-holiday {
    background-color: #e0e0e0 !important; /* light grey for holidays */
}
.fc .fc-weekend {
    background-color: #f0f0f0 !important;
}
.fc .fc-holiday-label {
  font-size: 14px;
  color: #a00;
  writing-mode: vertical-rl; /* vertical text */
  transform: rotate(180deg); /* read from top to bottom */
  position: absolute;
  top: 22px;
  left: 5px;
  z-index: 10;
  background-color: rgba(255, 255, 255, 0.7);
  padding: 2px;
  border-radius: 3px;
  max-height: 200px;
  overflow: hidden;
}
.fc .fc-daygrid-day-frame,
.fc .fc-timegrid-slot,
.fc .fc-day {
  position: relative;
}

Der Vollständigkeit halber:

Auch braucht man für den FULLCALENDAR diesen Eintrag auf Page-Level bei Javascript URL:
#WORKSPACE_FILES#fullcalendar_options.js

Die leicht angepasste Standard-Datei in den Shared Compoments sieht so aus:

function getCalendarOptions(regionId, holidaysItemId, pOptions) {
  // Load holidays JSON from page item
  const raw = $v(holidaysItemId);
  let holidays = [];
  try {
    holidays = JSON.parse(raw);
  } catch (e) {
    console.warn('Could not parse holidays JSON:', e);
  }
  // Modify existing pOptions IN-PLACE
  pOptions.locale = 'de';
  pOptions.initialView = 'timeGridWeek';
  pOptions.slotMinTime = "07:00:00";
  pOptions.slotMaxTime = "22:00:00";
  pOptions.expandRows = true;
  pOptions.height = '100%';
  pOptions.slotDuration = "01:00:00";
  pOptions.dayHeaderFormat = { weekday: 'short', month: 'numeric', day: 'numeric' };
  pOptions.titleFormat = { day: 'numeric', month: 'numeric', year: 'numeric' };
  pOptions.weekNumbers = false;
  pOptions.weekNumberCalculation = 'ISO';
  pOptions.displayEventTime = true;
  pOptions.displayEventEnd = true;
  pOptions.allDaySlot = false;
  pOptions.disableKeyboardSupport = true;
  pOptions.windowResize = null;
  pOptions.slotLabelFormat = { hour: '2-digit', minute: '2-digit', hour12: false };
  pOptions.eventTimeFormat = { hour: '2-digit', minute: '2-digit', hour12: false };
  pOptions.dayCellDidMount = function (info) {
    const day = info.date.getDay();
    // Style weekends
    if (day === 0 || day === 6) {
      info.el.classList.add('fc-weekend');
    }
    // Format date YYYY-MM-DD
    const dateStr = info.date.getFullYear() + '-' +
      String(info.date.getMonth() + 1).padStart(2, '0') + '-' +
      String(info.date.getDate()).padStart(2, '0');
    const holiday = holidays.find(h => h.date === dateStr);
    if (holiday) {
      info.el.classList.add('fc-holiday');
      if (
        info.view.type === 'timeGridWeek' &&
        info.el.classList.contains('fc-timegrid-col')
      ) {
        const label = document.createElement('div');
        label.className = 'fc-holiday-label';
        label.textContent = holiday.name;
        info.el.prepend(label);
      }
    }
  };
  return pOptions;
}

Viel Erfolg bei der Umsetzung!