feat(quick-260909-ab3): verstaendlicher Sync-Bericht, Konten ohne Adresse in der UI
- admin/ldap/page.tsx: SyncResult um emailConflicts/skippedNoLogin/ entryFailures erweitert; drei neue Berichtsabschnitte (Bernstein fuer Kollisionen, neutral fuer fehlenden Anmeldenamen, destruktiv fuer unerwartete Fehler) — kein englischer Techniktext, kein roher Datenbank-Wortlaut mehr im Bericht - GroupMembersModal.tsx: TenantUser.email optional, Suchvergleich gegen leere Adresse abgesichert (WINDOWS #15) — die Mitgliedersuche stuerzt nicht mehr ab, sobald ein Konto ohne Adresse existiert - users/page.tsx: User.email optional, Formular faellt auf leere Zeichenkette zurueck, Tabellenzelle zeigt einen Gedankenstrich; UserFormData.email bleibt bei Handanlage Pflicht - Neue Sprachschluessel unter admin.ldap.sync in de.json/en.json, Umlaut- und Sprachschluessel-Gate bestaetigt gruen (kein neues Allowlist-Wort noetig) - Neuer Testfall in groups-page.test.tsx vorab gegen den unveraenderten Bestand rot gelaufen; Abweichung von der Plan-Fixture dokumentiert (Kurzfassung: ein einzelnes Konto mit passendem Benutzernamen loest die Kollision wegen OR-Kurzschlussauswertung nie aus, ein zweites, nicht-treffendes Konto ohne Adresse schon — Vollfassung im SUMMARY) - 651/651 API- und 233/233 Web-Tests gruen, beide Typpruefungen sauber Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
This commit is contained in:
@@ -72,6 +72,13 @@ interface SyncResult {
|
||||
groupsRenamed: number;
|
||||
groupsDeleted: number;
|
||||
defaultMarkerMoved: number;
|
||||
// WINDOWS #15: Konten, die wegen einer bereits vergebenen Adresse ohne
|
||||
// diese Adresse angelegt/aktualisiert wurden; Kennungen ohne
|
||||
// Anmeldenamen (normaler Vorgang, kein Fehler); Kennungen mit einem
|
||||
// unerwarteten Fehler (technischer Wortlaut bleibt im Serverprotokoll).
|
||||
emailConflicts: { account: string; email: string }[];
|
||||
skippedNoLogin: string[];
|
||||
entryFailures: string[];
|
||||
errors: string[];
|
||||
}
|
||||
|
||||
@@ -1338,6 +1345,45 @@ export default function AdminLdapPage() {
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
{syncResult.emailConflicts.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-sm font-medium text-amber-700 dark:text-amber-400">
|
||||
{t('sync.emailConflictsHeading')}
|
||||
</p>
|
||||
{syncResult.emailConflicts.map((conflict, i) => (
|
||||
<p key={i} className="text-xs text-amber-700 dark:text-amber-400">
|
||||
{t('sync.emailConflictLine', {
|
||||
account: conflict.account,
|
||||
email: conflict.email,
|
||||
})}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{syncResult.skippedNoLogin.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-sm font-medium text-muted-foreground">
|
||||
{t('sync.skippedNoLoginHeading')}
|
||||
</p>
|
||||
{syncResult.skippedNoLogin.map((entry, i) => (
|
||||
<p key={i} className="text-xs text-muted-foreground">
|
||||
{entry}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{syncResult.entryFailures.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<p className="text-sm font-medium text-destructive">
|
||||
{t('sync.entryFailuresHeading')}
|
||||
</p>
|
||||
{syncResult.entryFailures.map((entry, i) => (
|
||||
<p key={i} className="text-xs text-destructive">
|
||||
{entry}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{syncResult.errors.length > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
{syncResult.errors.map((err, i) => (
|
||||
|
||||
Reference in New Issue
Block a user