feat(kalender): Kalender mit Urlauben und Feiertagen fuer alle

Neuer Menuepunkt "Kalender" fuer alle Rollen: genehmigte Urlaube aller
Mitarbeiter, auch in der Zukunft, dazu Feiertage. Monatsansicht (Standard)
und Zeitleiste, filterbar nach Team und Name.

- Eigene Tabelle pulse_calendar_days, gefuellt von lib/calendarSync.js im
  HRworks-Lauf (Fenster bis 400 Tage voraus). Buchungen entstehen weiter
  erst am jeweiligen Tag.
- Nur validatedOk und nur freigegebene Arten (calendar_types, Haken in den
  HRworks-Einstellungen). Krankmeldungen werden dafuer nicht abgefragt.
- GET /api/calendar fuer alle Angemeldeten, ohne Mails und Profilbilder.
- Timer laeuft zusaetzlich um 12:00 Uhr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
schakal00783
2026-09-25 11:37:26 +02:00
co-authored by Claude Opus 5.5
parent 6ae5a01bda
commit c0eb00faee
21 changed files with 996 additions and 33 deletions
+33 -4
View File
@@ -9,7 +9,8 @@ import { useToast } from '../Toast';
* Abwesenheiten aus HRworks.
*
* Urlaub, Krankheit, Elternzeit und Feiertage stehen in HRworks; Pulse holt sie
* einmal taeglich und bucht sie auf ein nicht abrechenbares Projekt. Hier
* zweimal taeglich, bucht sie auf ein nicht abrechenbares Projekt und fuellt
* den Kalender (nur Urlaub und Feiertage). Hier
* stehen die Zugangsdaten, die Zuordnung der Arten auf Tasks und der Knopf fuer
* einen Probelauf.
*/
@@ -44,10 +45,12 @@ export const HrworksSettings: React.FC<{ settings: AdminSettings; onSaved: () =>
setReport(r);
addToast(
was === 'apply'
? `Übernommen: ${r.created} neu, ${r.updated ?? 0} geändert, ${r.removed ?? 0} entfernt.`
? `Übernommen: ${r.created} neu, ${r.updated ?? 0} geändert, ${r.removed ?? 0} entfernt.` +
(r.calendar ? ` Kalender: ${r.calendar.vacation_days} Urlaubstage.` : '')
: `Probelauf: ${r.planned_create} neu, ${r.planned_update} geändert, ${r.planned_delete} zu entfernen.`,
'success'
);
if (r.calendar_error) addToast(`Kalender: ${r.calendar_error}`, 'error');
hrworksApi.status().then(setStatus).catch(() => undefined);
}
} catch (err) {
@@ -82,7 +85,7 @@ export const HrworksSettings: React.FC<{ settings: AdminSettings; onSaved: () =>
<Icon name={settings.hrworks.enabled ? 'check' : 'info'} className="mt-0.5 h-5 w-5 shrink-0" />
<span>
{settings.hrworks.enabled
? 'Der Abgleich läuft täglich um 5:30 Uhr und holt die letzten Tage nach.'
? 'Der Abgleich läuft täglich um 5:30 und 12:00 Uhr: Buchungen bis heute, dazu der Kalender mit den genehmigten Urlauben der Zukunft.'
: settings.hrworks.configured
? 'Zugangsdaten hinterlegt, der Abgleich ist noch nicht eingeschaltet.'
: 'Noch keine Zugangsdaten. Schlüsselpaar in HRworks unter Grundlagen → Integrationen → HRworks-API erzeugen.'}
@@ -148,7 +151,8 @@ export const HrworksSettings: React.FC<{ settings: AdminSettings; onSaved: () =>
<p className="mb-3 text-xs leading-snug text-ink-400">
Links der Schlüssel aus HRworks (<code className="rounded bg-ink-100 px-1">absence:</code> oder{' '}
<code className="rounded bg-ink-100 px-1">sick:</code>), rechts der Taskname in Pulse. Fehlt ein Eintrag, sucht Pulse
einen Task mit dem HRworks-Namen der Art – findet es keinen, meldet der Probelauf ihn als fehlend.
einen Task mit dem HRworks-Namen der Art – findet es keinen, meldet der Probelauf ihn als fehlend. Der Haken{' '}
<b>Kalender</b> legt fest, welche Arten im Kalender stehen, den alle Mitarbeiter sehen – Krankmeldungen nie.
</p>
<div className="grid gap-2 sm:grid-cols-2">
{eintraege.map(([key, value]) => (
@@ -159,6 +163,25 @@ export const HrworksSettings: React.FC<{ settings: AdminSettings; onSaved: () =>
value={value}
onChange={(e) => set({ type_map: { ...form.type_map, [key]: e.target.value } })}
/>
{key.startsWith('absence:') ? (
<label className="flex shrink-0 cursor-pointer items-center gap-1.5 text-[11px] font-bold text-ink-500" title="Im Kalender für alle zeigen">
<input
type="checkbox"
className="accent-[var(--color-brand-500)]"
checked={(form.calendar_types ?? []).includes(key)}
onChange={(e) =>
set({
calendar_types: e.target.checked
? [...(form.calendar_types ?? []), key]
: (form.calendar_types ?? []).filter((t) => t !== key),
})
}
/>
Kalender
</label>
) : (
<span className="w-[66px] shrink-0" />
)}
</div>
))}
</div>
@@ -193,6 +216,12 @@ export const HrworksSettings: React.FC<{ settings: AdminSettings; onSaved: () =>
: ''}
</p>
)}
{status?.calendar?.last_run && (
<p className="mt-1 text-xs text-ink-400">
Kalender: {new Date(status.calendar.last_run).toLocaleString('de-DE')} – {status.calendar.vacation_days} Urlaubstage,{' '}
{status.calendar.holidays} Feiertage.
</p>
)}
</Card>
</div>
);