fix(ui): Beschriftungszeile mit Diktatknopf richtet die Felder daneben aus
Im Abschnitt "Verlauf" der Opportunity begann "Was wurde besprochen?" tiefer als "Letzte Interaktion" daneben. Ursache war die Beschriftungszeile: rechts steht ein Diktatknopf darin, links nur Text - die Zeile war damit 26,5 statt 16,5 Pixel hoch und schob das Eingabefeld nach unten. Der Knopf bekommt eine feste Hoehe (h-7, 28 px) statt sich aus Zeilenhoehe, Innenabstand und Rahmen zu ergeben; auf 26,5 px laesst sich keine Zeile ausrichten. Dazu labelRowClass mit min-h-7: damit ist die Zeile in beiden Spalten gleich hoch, mit Knopf oder ohne. Gemessen sind es jetzt 0,0 px Abweichung statt 8. Zweiter Fund derselben Stelle: die drei Beschriftungen im Dialog waren von Hand abgeschrieben statt labelClass zu nehmen. Damit fehlte ihnen die Klasse "field-label", die seit den Themes die Schrift der Feldbeschriftungen traegt - in "Pixel Art" blieben sie ohne Pixelschrift. Die Auszeichnung steht jetzt einmal in leadUi.tsx (labelTextClass) und wird nicht mehr kopiert; DictateTextarea benutzt sie ebenso. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dc701f8522
commit
ab6b9f2372
@@ -227,7 +227,8 @@ Browser-Download.
|
|||||||
neues Theme stehen in [05 Frontend](05-frontend.md#themes-ohne-dark-varianten) -
|
neues Theme stehen in [05 Frontend](05-frontend.md#themes-ohne-dark-varianten) -
|
||||||
wichtigste davon: auf `brand-500`/`brand-600` steht weisse Schrift.
|
wichtigste davon: auf `brand-500`/`brand-600` steht weisse Schrift.
|
||||||
* **Gemeinsame Klassen** statt kopierter Utility-Ketten: `inputClass`,
|
* **Gemeinsame Klassen** statt kopierter Utility-Ketten: `inputClass`,
|
||||||
`labelClass` aus `components/leads/leadUi.tsx`, dazu `cardClass`,
|
`labelClass`, `labelTextClass`, `labelRowClass` aus
|
||||||
|
`components/leads/leadUi.tsx`, dazu `cardClass`,
|
||||||
`primaryButtonClass` aus `components/scanner/scannerUi.tsx`.
|
`primaryButtonClass` aus `components/scanner/scannerUi.tsx`.
|
||||||
* **Meldungen als Toast**, Rückfragen über `ConfirmDialog` - nie `alert()`
|
* **Meldungen als Toast**, Rückfragen über `ConfirmDialog` - nie `alert()`
|
||||||
oder `confirm()`. Rot (`tone="danger"`, Standard) nur, wenn etwas verloren
|
oder `confirm()`. Rot (`tone="danger"`, Standard) nur, wenn etwas verloren
|
||||||
|
|||||||
@@ -278,7 +278,7 @@ Warum genau diese Konstanten festgelegt sind, steht in
|
|||||||
| `components/acquisition/AcquisitionTrash.tsx` | Papierkorb der Akquise: zurückholen, endgültig löschen, leeren. Die Meldung nach dem Zurückholen nennt das Ziel (Eingang oder Archiv) - das Funddatum bleibt, also landet ein alter Fund im Archiv |
|
| `components/acquisition/AcquisitionTrash.tsx` | Papierkorb der Akquise: zurückholen, endgültig löschen, leeren. Die Meldung nach dem Zurückholen nennt das Ziel (Eingang oder Archiv) - das Funddatum bleibt, also landet ein alter Fund im Archiv |
|
||||||
| `components/acquisition/acquisitionUi.tsx` | `fieldClass` (abgesetzte Felder) und `OptionSelect` - zeigt einen Wert, der nicht mehr in der Liste steht, als solchen; `alwaysOffer` hält den eigenen Namen für *Von* immer wählbar. *Anbieter* nutzt es nicht: Freitext mit `<datalist>`. Dazu `ConsultantSelect` (mehrere Berater: Auswahlfeld hängt an, Plaketten nehmen weg) und `ConsultantList` für die Anzeige in der Liste |
|
| `components/acquisition/acquisitionUi.tsx` | `fieldClass` (abgesetzte Felder) und `OptionSelect` - zeigt einen Wert, der nicht mehr in der Liste steht, als solchen; `alwaysOffer` hält den eigenen Namen für *Von* immer wählbar. *Anbieter* nutzt es nicht: Freitext mit `<datalist>`. Dazu `ConsultantSelect` (mehrere Berater: Auswahlfeld hängt an, Plaketten nehmen weg) und `ConsultantList` für die Anzeige in der Liste |
|
||||||
| `components/settings/AcquisitionSettings.tsx` | Reiter *Einstellungen → Akquise*: die vier Auswahllisten pflegen |
|
| `components/settings/AcquisitionSettings.tsx` | Reiter *Einstellungen → Akquise*: die vier Auswahllisten pflegen |
|
||||||
| `components/leads/leadUi.tsx` | `inputClass`, `labelClass`, `KindBadge`, `Avatar`, Farben der Kontaktart |
|
| `components/leads/leadUi.tsx` | `inputClass`, `labelClass` (Beschriftung als eigene Zeile), `labelTextClass` + `labelRowClass` (Zeile mit Knopf rechts, z. B. Diktat - `min-h-7` haelt sie so hoch wie den Knopf, damit Felder in Nachbarspalten auf einer Linie beginnen), `KindBadge`, `Avatar`, Farben der Kontaktart |
|
||||||
| `components/leads/mailPool.ts` | `findMailPoolMatch()`, `diffFields()`, `isConflict()`, `MAIL_POOL_FIELDS` - Dublettenabgleich des Vorbereichs |
|
| `components/leads/mailPool.ts` | `findMailPoolMatch()`, `diffFields()`, `isConflict()`, `MAIL_POOL_FIELDS` - Dublettenabgleich des Vorbereichs |
|
||||||
| `components/leads/MailPoolLeads.tsx` | Ansicht „Aus Mails": Goldmarkierung, Abweichungen, Übernehmen, Sammelübernahme |
|
| `components/leads/MailPoolLeads.tsx` | Ansicht „Aus Mails": Goldmarkierung, Abweichungen, Übernehmen, Sammelübernahme |
|
||||||
| `components/leads/MailPoolMergeDialog.tsx` | Feldauswahl beim Ergänzen eines vorhandenen Kontakts |
|
| `components/leads/MailPoolMergeDialog.tsx` | Feldauswahl beim Ergänzen eines vorhandenen Kontakts |
|
||||||
|
|||||||
@@ -32,10 +32,25 @@ export const inputClass =
|
|||||||
/*
|
/*
|
||||||
* `field-label` ist keine Utility-Klasse, sondern ein Anker: darueber gibt ein
|
* `field-label` ist keine Utility-Klasse, sondern ein Anker: darueber gibt ein
|
||||||
* Theme allen Feldbeschriftungen eine andere Schrift (siehe index.css). Ohne
|
* Theme allen Feldbeschriftungen eine andere Schrift (siehe index.css). Ohne
|
||||||
* sie muesste jedes Label im Markup angefasst werden.
|
* sie muesste jedes Label im Markup angefasst werden - genau deshalb steht die
|
||||||
|
* Auszeichnung hier einmal und wird nicht abgeschrieben.
|
||||||
*/
|
*/
|
||||||
export const labelClass =
|
export const labelTextClass =
|
||||||
'field-label mb-1.5 block text-[11px] font-extrabold uppercase tracking-wider text-ink-500';
|
'field-label text-[11px] font-extrabold uppercase tracking-wider text-ink-500';
|
||||||
|
|
||||||
|
/** Beschriftung als eigene Zeile - der Normalfall. */
|
||||||
|
export const labelClass = `${labelTextClass} mb-1.5 block`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beschriftungszeile mit Platz fuer einen Knopf rechts (Diktat).
|
||||||
|
*
|
||||||
|
* `min-h-7` ist der Punkt: der Diktatknopf ist 28 px hoch, eine nackte
|
||||||
|
* Beschriftung nur 16,5 px. Ohne die Mindesthoehe beginnt das Eingabefeld
|
||||||
|
* daneben ueber 10 px tiefer als das in der Nachbarspalte - im Abschnitt
|
||||||
|
* "Verlauf" der Opportunity war genau das zu sehen. Die Hoehe des Knopfes ist
|
||||||
|
* dafuer in DictationField.tsx auf denselben Wert festgelegt.
|
||||||
|
*/
|
||||||
|
export const labelRowClass = 'mb-1.5 flex min-h-7 items-center justify-between gap-3';
|
||||||
|
|
||||||
export const Avatar: React.FC<{
|
export const Avatar: React.FC<{
|
||||||
src?: string | null;
|
src?: string | null;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||||||
import { Modal } from '../ui/Modal';
|
import { Modal } from '../ui/Modal';
|
||||||
import { LeadModal } from '../leads/LeadModal';
|
import { LeadModal } from '../leads/LeadModal';
|
||||||
import { DictateTextarea, DictateButton, DictationHint } from '../ui/DictationField';
|
import { DictateTextarea, DictateButton, DictationHint } from '../ui/DictationField';
|
||||||
import { Avatar, inputClass, labelClass } from '../leads/leadUi';
|
import { Avatar, inputClass, labelClass, labelRowClass, labelTextClass } from '../leads/leadUi';
|
||||||
import { STAGE_COLORS, STAGE_HINTS, STAGE_LABELS } from './opportunityUi';
|
import { STAGE_COLORS, STAGE_HINTS, STAGE_LABELS } from './opportunityUi';
|
||||||
import { useToast } from '../Toast';
|
import { useToast } from '../Toast';
|
||||||
import { acquisitionOptionsApi, opportunitiesApi } from '../../lib/api';
|
import { acquisitionOptionsApi, opportunitiesApi } from '../../lib/api';
|
||||||
@@ -590,9 +590,16 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
|||||||
</h3>
|
</h3>
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
<div>
|
<div>
|
||||||
<label className={labelClass} htmlFor="opp-interaction">
|
{/*
|
||||||
|
Dieselbe Zeile wie beim Feld daneben (labelRowClass), obwohl hier
|
||||||
|
kein Knopf steht: sonst beginnt das Datumsfeld 8 px hoeher als
|
||||||
|
die Notiz rechts - der Diktatknopf macht die Nachbarzeile hoeher.
|
||||||
|
*/}
|
||||||
|
<div className={labelRowClass}>
|
||||||
|
<label className={labelTextClass} htmlFor="opp-interaction">
|
||||||
Letzte Interaktion
|
Letzte Interaktion
|
||||||
</label>
|
</label>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
id="opp-interaction"
|
id="opp-interaction"
|
||||||
type="date"
|
type="date"
|
||||||
@@ -602,8 +609,8 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
<div className="mb-1.5 flex items-center justify-between gap-3">
|
<div className={labelRowClass}>
|
||||||
<label className="block text-[11px] font-extrabold uppercase tracking-wider text-ink-500" htmlFor="opp-interaction-note">
|
<label className={labelTextClass} htmlFor="opp-interaction-note">
|
||||||
Was wurde besprochen?
|
Was wurde besprochen?
|
||||||
</label>
|
</label>
|
||||||
<DictateButton
|
<DictateButton
|
||||||
@@ -631,8 +638,8 @@ export const OpportunityModal: React.FC<OpportunityModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="mb-1.5 flex items-center justify-between gap-3">
|
<div className={labelRowClass}>
|
||||||
<label className="block text-[11px] font-extrabold uppercase tracking-wider text-ink-500" htmlFor="opp-next">
|
<label className={labelTextClass} htmlFor="opp-next">
|
||||||
Nächster Schritt
|
Nächster Schritt
|
||||||
</label>
|
</label>
|
||||||
<DictateButton
|
<DictateButton
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useRecorder } from '../../lib/dictation';
|
|||||||
import { useWhisper } from '../../lib/whisper';
|
import { useWhisper } from '../../lib/whisper';
|
||||||
import { useNavigation } from '../../lib/navigation';
|
import { useNavigation } from '../../lib/navigation';
|
||||||
import { useToast } from '../Toast';
|
import { useToast } from '../Toast';
|
||||||
import { inputClass } from '../leads/leadUi';
|
import { inputClass, labelRowClass, labelTextClass } from '../leads/leadUi';
|
||||||
|
|
||||||
const MIC_ICON =
|
const MIC_ICON =
|
||||||
'M19 11a7 7 0 01-14 0m7 7v3m0-3a7 7 0 007-7m-7 7a7 7 0 01-7-7m7-9a3 3 0 013 3v6a3 3 0 01-6 0V5a3 3 0 013-3z';
|
'M19 11a7 7 0 01-14 0m7 7v3m0-3a7 7 0 007-7m-7 7a7 7 0 01-7-7m7-9a3 3 0 013 3v6a3 3 0 01-6 0V5a3 3 0 013-3z';
|
||||||
@@ -52,7 +52,9 @@ export const DictateButton: React.FC<DictateButtonProps> = ({
|
|||||||
goTo('Einstellungen', { tab: 'whisper' });
|
goTo('Einstellungen', { tab: 'whisper' });
|
||||||
}}
|
}}
|
||||||
title="Das Spracherkennungsmodell ist noch nicht geladen - hier einrichten"
|
title="Das Spracherkennungsmodell ist noch nicht geladen - hier einrichten"
|
||||||
className="flex items-center gap-1.5 rounded-lg border border-dashed border-ink-300 px-2 py-1 text-[11px] font-bold text-ink-400 transition hover:border-brand-400 hover:text-brand-700"
|
// Dieselbe Hoehe wie der Diktatknopf, damit die Beschriftungszeile in
|
||||||
|
// beiden Zustaenden gleich hoch bleibt.
|
||||||
|
className="flex h-7 items-center gap-1.5 rounded-lg border border-dashed border-ink-300 px-2 text-[11px] font-bold text-ink-400 transition hover:border-brand-400 hover:text-brand-700"
|
||||||
>
|
>
|
||||||
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={MIC_ICON} />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={MIC_ICON} />
|
||||||
@@ -79,7 +81,14 @@ export const DictateButton: React.FC<DictateButtonProps> = ({
|
|||||||
: `${label} diktieren`
|
: `${label} diktieren`
|
||||||
}
|
}
|
||||||
aria-label={isRecording ? 'Aufnahme beenden' : `${label} diktieren`}
|
aria-label={isRecording ? 'Aufnahme beenden' : `${label} diktieren`}
|
||||||
className={`flex items-center gap-1.5 rounded-lg border px-2 py-1 text-[11px] font-bold transition disabled:opacity-60 ${
|
/*
|
||||||
|
* `h-7` (28 px) statt sich aus Zeilenhoehe, Innenabstand und Rahmen zu
|
||||||
|
* ergeben: das ergab 26,5 px, und eine Beschriftungszeile laesst sich
|
||||||
|
* nicht auf einen halben Pixel ausrichten. Mit der runden Hoehe passt
|
||||||
|
* labelRowClass (min-h-7) genau, und das Feld daneben beginnt auf
|
||||||
|
* derselben Linie.
|
||||||
|
*/
|
||||||
|
className={`flex h-7 items-center gap-1.5 rounded-lg border px-2 text-[11px] font-bold transition disabled:opacity-60 ${
|
||||||
isRecording
|
isRecording
|
||||||
? 'border-red-300 bg-red-50 text-red-700'
|
? 'border-red-300 bg-red-50 text-red-700'
|
||||||
: 'border-ink-200 text-ink-500 hover:border-brand-300 hover:text-brand-700'
|
: 'border-ink-200 text-ink-500 hover:border-brand-300 hover:text-brand-700'
|
||||||
@@ -218,11 +227,8 @@ export const DictateTextarea: React.FC<DictateTextareaProps> = ({
|
|||||||
onBeforeNavigate,
|
onBeforeNavigate,
|
||||||
}) => (
|
}) => (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-1.5 flex items-center justify-between gap-3">
|
<div className={labelRowClass}>
|
||||||
<label
|
<label htmlFor={id} className={labelTextClass}>
|
||||||
htmlFor={id}
|
|
||||||
className="block text-[11px] font-extrabold uppercase tracking-wider text-ink-500"
|
|
||||||
>
|
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
<DictateButton
|
<DictateButton
|
||||||
|
|||||||
Reference in New Issue
Block a user