fix(dashboard): Bilderrahmen - "1 Stunde" statt "60 Minuten", Bildanzahl in der Einstellungs-Kopfzeile
Zwei Kosmetik-Punkte nach dem Browser-Rundgang vom 21.09.2026: - Das laengste Wechselintervall (3600 s) hiess "60 Minuten", beim XFrame heisst dieselbe Stufe "Jede Stunde". Neuer Schluessel `pictureFrame.intervalHours` (ICU-Plural, de + en). - Unter Einstellungen -> Dashboard zeigte "XFrame #1 - Board" seinen Titel, "Bilderrahmen #1" nichts. Die Kopfzeile nennt jetzt "- 1 Bild" bzw. "- N Bilder" (Schluessel `imageCountOne`/`imageCountMany`, zwei Schluessel statt ICU, weil die Panel-Tests eine einfache Uebersetzungs-Attrappe nutzen). Web-Tests 603 -> 604, type-check und lint unveraendert gruen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,8 @@ describe('PictureFrameConfigForm (quick-260921-pi9)', () => {
|
|||||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('5 Sekunden');
|
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('5 Sekunden');
|
||||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('30 Minuten');
|
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('30 Minuten');
|
||||||
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('1 Minute');
|
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('1 Minute');
|
||||||
|
expect(Array.from(interval.options).map((o) => o.textContent)).toContain('1 Stunde');
|
||||||
|
expect(Array.from(interval.options).map((o) => o.textContent)).not.toContain('60 Minuten');
|
||||||
|
|
||||||
fireEvent.change(fit, { target: { value: 'cover' } });
|
fireEvent.change(fit, { target: { value: 'cover' } });
|
||||||
expect(onChange).toHaveBeenLastCalledWith({ fit: 'cover' });
|
expect(onChange).toHaveBeenLastCalledWith({ fit: 'cover' });
|
||||||
|
|||||||
@@ -128,6 +128,8 @@ export function PictureFrameConfigForm({ config, onChange }: PictureFrameConfigF
|
|||||||
function intervalLabel(seconds: number): string {
|
function intervalLabel(seconds: number): string {
|
||||||
if (seconds === 0) return t('pictureFrame.intervalOff');
|
if (seconds === 0) return t('pictureFrame.intervalOff');
|
||||||
if (seconds < 60) return t('pictureFrame.intervalSeconds', { n: seconds });
|
if (seconds < 60) return t('pictureFrame.intervalSeconds', { n: seconds });
|
||||||
|
// 3600 s heisst wie beim XFrame „1 Stunde“, nicht „60 Minuten“.
|
||||||
|
if (seconds >= 3600) return t('pictureFrame.intervalHours', { n: seconds / 3600 });
|
||||||
return t('pictureFrame.intervalMinutes', { n: seconds / 60 });
|
return t('pictureFrame.intervalMinutes', { n: seconds / 60 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -263,3 +263,25 @@ describe('WidgetSettingsPanel — XFrame (quick-260921-qd3)', () => {
|
|||||||
expect(updateWidgetConfig).toHaveBeenCalledWith('x1', { url: 'https://b.de/' });
|
expect(updateWidgetConfig).toHaveBeenCalledWith('x1', { url: 'https://b.de/' });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('WidgetSettingsPanel — Bilderrahmen-Kopfzeile (Kosmetik nach quick-260921-pi9)', () => {
|
||||||
|
it('Test P1: Kopfzeile zeigt "— 1 Bild" bzw. "— 3 Bilder", ohne Bilder keinen Zusatz', () => {
|
||||||
|
const one = { id: 'p1', widgetType: 'picture-frame', config: { images: [{ kind: 'url', url: 'https://a.de/x.png' }] } };
|
||||||
|
const three = {
|
||||||
|
id: 'p2',
|
||||||
|
widgetType: 'picture-frame',
|
||||||
|
config: {
|
||||||
|
images: [
|
||||||
|
{ kind: 'url', url: 'https://a.de/1.png' },
|
||||||
|
{ kind: 'upload', imageId: 'b' },
|
||||||
|
{ kind: 'url', url: 'https://a.de/3.png' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const none = { id: 'p3', widgetType: 'picture-frame', config: {} };
|
||||||
|
render(<WidgetSettingsPanel widgets={[one, three, none]} onWidgetUpdate={vi.fn()} />);
|
||||||
|
expect(screen.getByRole('button', { name: /Bilderrahmen #1/ })).toHaveTextContent('— 1 Bild');
|
||||||
|
expect(screen.getByRole('button', { name: /Bilderrahmen #2/ })).toHaveTextContent('— 3 Bilder');
|
||||||
|
expect(screen.getByRole('button', { name: /Bilderrahmen #3/ })).not.toHaveTextContent('—');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
CALENDAR_MAX_EVENTS_LIMIT,
|
CALENDAR_MAX_EVENTS_LIMIT,
|
||||||
resolveCalendarConfig,
|
resolveCalendarConfig,
|
||||||
} from '@/components/dashboard/widgets/calendar-month';
|
} from '@/components/dashboard/widgets/calendar-month';
|
||||||
|
import { resolvePictureFrameConfig } from '@/components/dashboard/widgets/picture-frame-config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IANA timezones offered for clock configuration (D-12).
|
* IANA timezones offered for clock configuration (D-12).
|
||||||
@@ -128,6 +129,18 @@ export function WidgetSettingsPanel({
|
|||||||
— {widget.config.title.trim()}
|
— {widget.config.title.trim()}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{/* Bilderrahmen hat keinen Titel — die Kopfzeile nennt die Bildanzahl */}
|
||||||
|
{widget.widgetType === 'picture-frame' &&
|
||||||
|
resolvePictureFrameConfig(widget.config).images.length > 0 && (
|
||||||
|
<span className="ml-2 text-muted-foreground">
|
||||||
|
—{' '}
|
||||||
|
{resolvePictureFrameConfig(widget.config).images.length === 1
|
||||||
|
? t('pictureFrame.imageCountOne')
|
||||||
|
: t('pictureFrame.imageCountMany', {
|
||||||
|
n: resolvePictureFrameConfig(widget.config).images.length,
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -348,10 +348,13 @@
|
|||||||
"intervalOff": "Kein Wechsel",
|
"intervalOff": "Kein Wechsel",
|
||||||
"intervalSeconds": "{n} Sekunden",
|
"intervalSeconds": "{n} Sekunden",
|
||||||
"intervalMinutes": "{n, plural, one {# Minute} other {# Minuten}}",
|
"intervalMinutes": "{n, plural, one {# Minute} other {# Minuten}}",
|
||||||
|
"intervalHours": "{n, plural, one {# Stunde} other {# Stunden}}",
|
||||||
"orderLabel": "Reihenfolge der Bilder",
|
"orderLabel": "Reihenfolge der Bilder",
|
||||||
"orderSequence": "Reihenfolge",
|
"orderSequence": "Reihenfolge",
|
||||||
"orderRandom": "Zufall",
|
"orderRandom": "Zufall",
|
||||||
"imagesLabel": "Bilder",
|
"imagesLabel": "Bilder",
|
||||||
|
"imageCountOne": "1 Bild",
|
||||||
|
"imageCountMany": "{n} Bilder",
|
||||||
"captionPlaceholder": "Bildunterschrift (optional)",
|
"captionPlaceholder": "Bildunterschrift (optional)",
|
||||||
"uploadButton": "Bild hochladen",
|
"uploadButton": "Bild hochladen",
|
||||||
"uploadHint": "PNG, JPEG, GIF oder WebP, höchstens 5 MB, bis zu 30 Bilder",
|
"uploadHint": "PNG, JPEG, GIF oder WebP, höchstens 5 MB, bis zu 30 Bilder",
|
||||||
|
|||||||
@@ -348,10 +348,13 @@
|
|||||||
"intervalOff": "No change",
|
"intervalOff": "No change",
|
||||||
"intervalSeconds": "{n} seconds",
|
"intervalSeconds": "{n} seconds",
|
||||||
"intervalMinutes": "{n, plural, one {# minute} other {# minutes}}",
|
"intervalMinutes": "{n, plural, one {# minute} other {# minutes}}",
|
||||||
|
"intervalHours": "{n, plural, one {# hour} other {# hours}}",
|
||||||
"orderLabel": "Image order",
|
"orderLabel": "Image order",
|
||||||
"orderSequence": "In order",
|
"orderSequence": "In order",
|
||||||
"orderRandom": "Random",
|
"orderRandom": "Random",
|
||||||
"imagesLabel": "Images",
|
"imagesLabel": "Images",
|
||||||
|
"imageCountOne": "1 image",
|
||||||
|
"imageCountMany": "{n} images",
|
||||||
"captionPlaceholder": "Caption (optional)",
|
"captionPlaceholder": "Caption (optional)",
|
||||||
"uploadButton": "Upload image",
|
"uploadButton": "Upload image",
|
||||||
"uploadHint": "PNG, JPEG, GIF or WebP, at most 5 MB, up to 30 images",
|
"uploadHint": "PNG, JPEG, GIF or WebP, at most 5 MB, up to 30 images",
|
||||||
|
|||||||
Reference in New Issue
Block a user