feat: implement hide app name feature with toggle in admin settings and update branding context
Some checks failed
Build & Push Docker Image / build (push) Has been cancelled

This commit is contained in:
2026-03-04 10:11:35 +01:00
parent bac4e8ae7c
commit ce2cf499dc
11 changed files with 627 additions and 61 deletions

View File

@@ -8,7 +8,7 @@ const sizes = {
};
export default function BrandLogo({ size = 'md', className = '' }) {
const { appName, hasLogo, logoUrl } = useBranding();
const { appName, hasLogo, logoUrl, hideAppName } = useBranding();
const s = sizes[size] || sizes.md;
if (hasLogo && logoUrl) {
@@ -19,7 +19,7 @@ export default function BrandLogo({ size = 'md', className = '' }) {
alt={appName}
className={`${s.box} ${s.rounded} object-contain`}
/>
<span className={`${s.text} font-bold gradient-text`}>{appName}</span>
{!hideAppName && <span className={`${s.text} font-bold gradient-text`}>{appName}</span>}
</div>
);
}

View File

@@ -0,0 +1,126 @@
import ReactDatePicker from 'react-datepicker';
import { de } from 'date-fns/locale';
import { Calendar as CalendarIcon, Clock, X } from 'lucide-react';
import { forwardRef } from 'react';
import 'react-datepicker/dist/react-datepicker.css';
/**
* Custom DateTimePicker that reads the app's CSS variables and
* fully matches whatever theme is active.
*
* Props:
* value ISO datetime string (or '')
* onChange (isoString) => void
* label string
* required bool
* minDate Date | null
* icon 'calendar' (default) | 'clock'
*/
export default function DateTimePicker({
value,
onChange,
label,
required = false,
minDate = null,
icon = 'calendar',
}) {
const selected = value ? new Date(value) : null;
const handleChange = (date) => {
if (!date) { onChange(''); return; }
// Produce local datetime string yyyy-MM-ddTHH:mm
const y = date.getFullYear();
const mo = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const mi = String(date.getMinutes()).padStart(2, '0');
onChange(`${y}-${mo}-${d}T${h}:${mi}`);
};
const Icon = icon === 'clock' ? Clock : CalendarIcon;
return (
<div className="dtp-wrapper">
{label && (
<label className="block text-sm font-medium text-th-text mb-1.5">
{label}{required && ' *'}
</label>
)}
<div className="dtp-input-wrap">
<Icon size={15} className="dtp-icon" />
<ReactDatePicker
selected={selected}
onChange={handleChange}
showTimeSelect
timeFormat="HH:mm"
timeIntervals={15}
dateFormat="dd.MM.yyyy HH:mm"
locale={de}
minDate={minDate}
required={required}
popperPlacement="bottom-start"
popperModifiers={[
{ name: 'offset', options: { offset: [0, 4] } },
{ name: 'preventOverflow', options: { rootBoundary: 'viewport', tether: false, altAxis: true } },
]}
customInput={<CustomInput />}
calendarClassName="dtp-calendar"
dayClassName={(date) => 'dtp-day'}
timeClassName={() => 'dtp-time-item'}
renderCustomHeader={CalendarHeader}
isClearable
clearButtonClassName="dtp-clear-btn"
wrapperClassName="dtp-dp-wrapper"
/>
</div>
</div>
);
}
// ── Custom Input ─────────────────────────────────────────────────────────────
const CustomInput = forwardRef(({ value, onClick, onClear, ...rest }, ref) => (
<button
ref={ref}
type="button"
onClick={onClick}
className="dtp-custom-input"
{...rest}
>
<span className={value ? 'dtp-value' : 'dtp-placeholder'}>
{value || 'Datum & Uhrzeit wählen …'}
</span>
</button>
));
CustomInput.displayName = 'CustomInput';
// ── Custom Header ─────────────────────────────────────────────────────────────
function CalendarHeader({
date,
decreaseMonth, increaseMonth,
prevMonthButtonDisabled, nextMonthButtonDisabled,
}) {
const label = date.toLocaleString('de', { month: 'long', year: 'numeric' });
return (
<div className="dtp-header">
<button
type="button"
onClick={decreaseMonth}
disabled={prevMonthButtonDisabled}
className="dtp-nav-btn"
aria-label="Vorheriger Monat"
>
</button>
<span className="dtp-month-label">{label}</span>
<button
type="button"
onClick={increaseMonth}
disabled={nextMonthButtonDisabled}
className="dtp-nav-btn"
aria-label="Nächster Monat"
>
</button>
</div>
);
}