feat: mobile Responsiveness für Suche und Mail-Detailansicht

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-06-13 13:18:19 +02:00
parent bc82854165
commit cca27c663a
3 changed files with 25 additions and 18 deletions
+2 -2
View File
@@ -24,7 +24,7 @@
| PROJ-10 | Admin-Bereich: Nutzer- & Postfachverwaltung | Deployed | [PROJ-10](PROJ-10-admin-bereich.md) | 2026-03-12 |
| PROJ-11 | Audit-Log & Compliance-Berichte | Deployed | [PROJ-11](PROJ-11-audit-log.md) | 2026-03-12 |
| PROJ-12 | E-Mail-Export (EML/PDF) | Deployed | [PROJ-12](PROJ-12-export.md) | 2026-03-12 |
| PROJ-13 | REST API für externe CRM-Anbindung | In Progress | [PROJ-13](PROJ-13-rest-api-crm.md) | 2026-03-13 |
| PROJ-13 | REST API für externe CRM-Anbindung | Deployed | [PROJ-13](PROJ-13-rest-api-crm.md) | 2026-03-13 |
| PROJ-14 | E-Mail-Import: POP3-Verbindung | Deployed | [PROJ-14](PROJ-14-import-pop3.md) | 2026-03-13 |
| PROJ-15 | CLI Import & Export (archivmail-User) | Deployed | [PROJ-15](PROJ-15-cli-import-export.md) | 2026-03-13 |
| PROJ-16 | LDAP / Active Directory Anbindung | Deployed | [PROJ-16](PROJ-16-ldap-active-directory.md) | 2026-03-13 |
@@ -42,7 +42,7 @@
| PROJ-26 | IMAP-Server-Schnittstelle (Read-Only Archivzugriff) | Deployed | [PROJ-26](PROJ-26-imap-server-schnittstelle.md) | 2026-03-18 |
| PROJ-27 | Container-Ready (Dockerfile + Env-Vars) | In Review | [PROJ-27](PROJ-27-container-ready.md) | 2026-03-28 |
| PROJ-28 | Self-Service Onboarding (Sign-up, E-Mail-Verifikation, Passwort-Reset) | In Progress | [PROJ-28](PROJ-28-self-service-onboarding.md) | 2026-03-28 |
| PROJ-28 | Self-Service Onboarding (Sign-up, E-Mail-Verifikation, Passwort-Reset) | Deployed | [PROJ-28](PROJ-28-self-service-onboarding.md) | 2026-03-28 |
| PROJ-29 | Tenant-Quotas & Usage-Limits | Deployed | [PROJ-29](PROJ-29-tenant-quotas.md) | 2026-03-28 |
| PROJ-30 | Volltext-Index: Xapian → Manticore Search Migration | Deployed | [PROJ-30](PROJ-30-bleve-migration.md) | 2026-03-28 |
| PROJ-31 | Billing & Subscriptions (Stripe) | Planned | [PROJ-31](PROJ-31-billing-subscriptions.md) | 2026-03-28 |
+4 -3
View File
@@ -168,11 +168,12 @@ function MailBodyView({ mail }: { mail: MailDetail }) {
<div className="space-y-2">
{!showExternal && (
<Alert>
<AlertDescription className="flex items-center justify-between gap-4 text-sm">
<AlertDescription className="flex flex-col items-start gap-2 text-sm sm:flex-row sm:items-center sm:justify-between sm:gap-4">
<span>Externe Inhalte (Bilder, Tracker) sind blockiert.</span>
<Button
variant="outline"
size="sm"
className="w-full sm:w-auto"
onClick={() => setShowExternal(true)}
>
Externe Inhalte laden
@@ -346,8 +347,8 @@ export default function MailViewPage({
</Button>
{mail && (
<div className="flex items-center gap-2">
<Badge variant="outline" className="font-mono text-xs">
<div className="flex w-full flex-wrap items-center gap-2 sm:w-auto">
<Badge variant="outline" className="max-w-[40vw] truncate font-mono text-xs sm:max-w-none">
{id}
</Badge>
<Button
+19 -13
View File
@@ -383,18 +383,18 @@ export default function SearchPage() {
{/* Main content */}
<div className="flex-1 min-w-0">
<form onSubmit={handleSubmit} className="space-y-4">
<div className="flex gap-2">
<div className="flex flex-wrap items-center gap-2">
<Input
placeholder="Volltextsuche..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="flex-1"
className="w-full flex-1 sm:w-auto"
aria-label="Suchbegriff"
/>
<Button type="submit" disabled={searching}>
<Button type="submit" disabled={searching} className="flex-1 sm:flex-none">
{searching ? "Suche..." : "Suchen"}
</Button>
<Button type="button" variant="outline" onClick={() => setUploadOpen(true)}>
<Button type="button" variant="outline" onClick={() => setUploadOpen(true)} className="flex-1 sm:flex-none">
Importieren
</Button>
{hasActiveSearch && (
@@ -596,7 +596,7 @@ export default function SearchPage() {
</Button>
</div>
<Card>
<Card className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
@@ -610,12 +610,12 @@ export default function SearchPage() {
aria-label="Alle auswählen"
/>
</TableHead>
<TableHead className="w-32">Datum</TableHead>
<TableHead className="w-56">Von</TableHead>
<TableHead className="w-28 sm:w-32">Datum</TableHead>
<TableHead className="hidden w-56 md:table-cell">Von</TableHead>
<TableHead>Betreff</TableHead>
<TableHead className="w-48">An</TableHead>
<TableHead className="hidden w-48 lg:table-cell">An</TableHead>
<TableHead className="w-8 text-center" title="Anhang">📎</TableHead>
<TableHead className="w-20 text-right">Größe</TableHead>
<TableHead className="hidden w-20 text-right sm:table-cell">Größe</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@@ -650,8 +650,8 @@ export default function SearchPage() {
? new Date(hit.date).toLocaleString("de-DE", { dateStyle: "short", timeStyle: "short" })
: "-"}
</TableCell>
<TableCell className="max-w-[14rem] truncate text-sm">{hit.from || "-"}</TableCell>
<TableCell className="font-medium">
<TableCell className="hidden max-w-[14rem] truncate text-sm md:table-cell">{hit.from || "-"}</TableCell>
<TableCell className="max-w-[60vw] font-medium sm:max-w-none">
<div className="flex items-center gap-2">
<span className="truncate">{hit.subject || "(kein Betreff)"}</span>
{hit.thread_size && hit.thread_size > 1 && (
@@ -660,13 +660,19 @@ export default function SearchPage() {
</span>
)}
</div>
{/* Absender nur auf Mobile, da Spalte "Von" dort ausgeblendet ist */}
{hit.from && (
<div className="mt-0.5 truncate text-xs text-muted-foreground md:hidden">
{hit.from}
</div>
)}
<SnippetLine hit={hit} />
</TableCell>
<TableCell className="max-w-[12rem] truncate text-sm text-muted-foreground">{hit.to || "-"}</TableCell>
<TableCell className="hidden max-w-[12rem] truncate text-sm text-muted-foreground lg:table-cell">{hit.to || "-"}</TableCell>
<TableCell className="text-center text-sm">
{hit.has_attachments ? "📎" : ""}
</TableCell>
<TableCell className="text-right text-xs text-muted-foreground whitespace-nowrap">
<TableCell className="hidden text-right text-xs text-muted-foreground whitespace-nowrap sm:table-cell">
{hit.size ? formatBytes(hit.size) : ""}
</TableCell>
</TableRow>