T08c: Frontend Mail UI + Global Search UI — 44 tests, tsc clean, vite build pass

- Mail page: 3-pane layout (folder tree + mail list + reading pane)
- Compose modal: rich text editor (bold/italic/link), template picker, reply/forward pre-fill
- Mail settings: accounts, signatures, rules, labels, vacation, PGP (6 tabs)
- Shared mailbox selector: switch between personal + shared accounts
- Mail search bar + attachment download + create-event-from-mail
- Global search: tabs for companies/contacts/mails/files/events
- Search autocomplete in TopBar (existing SearchDropdown)
- API client: mail.ts (all endpoints)
- Routes: /mail, /mail/settings
- i18n: de.json + en.json mail + search translations
- 44 new tests (4 test files), full regression 318/318 pass
- tsc --noEmit: 0 errors, vite build: 267 modules
This commit is contained in:
leocrm-bot
2026-07-01 20:43:49 +02:00
parent 0962f3a961
commit 0070fb3aea
30 changed files with 4312 additions and 191 deletions
@@ -0,0 +1,46 @@
/**
* Mail search bar — input for full-text mail search.
*/
import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Input } from '@/components/ui/Input';
export interface MailSearchBarProps {
onSearch: (query: string) => void;
}
export function MailSearchBar({ onSearch }: MailSearchBarProps) {
const { t } = useTranslation();
const [query, setQuery] = useState('');
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setQuery(e.target.value);
}, []);
const handleSubmit = useCallback((e: React.FormEvent) => {
e.preventDefault();
onSearch(query.trim());
}, [query, onSearch]);
return (
<form onSubmit={handleSubmit} className="relative" data-testid="mail-search-bar">
<Input
type="search"
value={query}
onChange={handleChange}
placeholder={t('mail.searchPlaceholder')}
aria-label={t('common.search')}
/>
<button
type="submit"
className="absolute right-2 top-1/2 -translate-y-1/2 p-1.5 rounded-md hover:bg-secondary-100 min-h-touch min-w-touch"
aria-label={t('common.search')}
>
<svg className="w-4 h-4 text-secondary-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
</form>
);
}