UI: Suchfeld 10% niedriger, Kontakt-Baum mit Firmen/Privatpersonen Trennung, Toolbar vereinfacht (Sort+View Toggle)
This commit is contained in:
@@ -6,10 +6,9 @@
|
||||
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx';
|
||||
import { ResizablePanel } from '@/components/ui/ResizablePanel';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { Select } from '@/components/ui/Select';
|
||||
import { useToast } from '@/components/ui/Toast';
|
||||
import { EmptyState } from '@/components/ui/EmptyState';
|
||||
import { usePluginToolbarStore } from '@/store/pluginToolbarStore';
|
||||
@@ -232,33 +231,80 @@ export function ContactsListPage() {
|
||||
className="border-r border-secondary-200 bg-white"
|
||||
data-testid="contact-list-pane"
|
||||
>
|
||||
{/* Toolbar */}
|
||||
<div className="flex items-center gap-2 px-3 py-2 border-b border-secondary-200 bg-white flex-shrink-0">
|
||||
<Input
|
||||
type="search"
|
||||
value={search}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
placeholder={t('contacts.searchPlaceholder')}
|
||||
aria-label={t('common.search')}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Select
|
||||
value={viewMode}
|
||||
onChange={(e) => setViewMode(e.target.value as ContactViewMode)}
|
||||
options={viewModeOptions}
|
||||
aria-label={t('contacts.viewMode')}
|
||||
className="w-auto"
|
||||
/>
|
||||
<Select
|
||||
value={sortBy}
|
||||
onChange={(e) => { setSortBy(e.target.value); setPage(1); }}
|
||||
options={sortOptions}
|
||||
aria-label={t('common.filter')}
|
||||
className="w-auto"
|
||||
/>
|
||||
<Button size="sm" onClick={handleCreate} data-testid="contacts-new-btn">
|
||||
{t('contacts.create')}
|
||||
</Button>
|
||||
{/* Toolbar — minimal: sort toggle + view toggle + new */}
|
||||
<div className="flex items-center gap-1 px-3 py-1.5 border-b border-secondary-200 bg-white flex-shrink-0">
|
||||
{/* Sort */}
|
||||
<div className="flex items-center gap-1">
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={(e) => { setSortBy(e.target.value); setPage(1); }}
|
||||
className="text-xs border border-secondary-300 rounded-md px-2 py-1 bg-white text-secondary-700 focus:outline-none focus:ring-1 focus:ring-primary-500"
|
||||
aria-label={t('common.sort')}
|
||||
>
|
||||
{sortOptions.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => handleSortChange(sortBy, sortOrder === 'asc' ? 'desc' : 'asc')}
|
||||
className="p-1.5 rounded-md hover:bg-secondary-100 min-h-touch min-w-touch flex items-center justify-center"
|
||||
aria-label={sortOrder === 'asc' ? t('common.sortDesc') : t('common.sortAsc')}
|
||||
title={sortOrder === 'asc' ? 'Absteigend' : 'Aufsteigend'}
|
||||
>
|
||||
<svg className="w-3.5 h-3.5 text-secondary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d={sortOrder === 'asc' ? 'M3 4h13M3 8h9M3 12h5m13 0l-4 4m4-4l-4-4m4 4v6' : 'M3 4h13M3 8h9M3 12h5m13-4v6m0 0l-4-4m4 4l-4 4'} />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* View mode toggle */}
|
||||
<div className="flex items-center gap-0.5 bg-secondary-100 rounded-md p-0.5">
|
||||
{viewModeOptions.map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => setViewMode(opt.value as ContactViewMode)}
|
||||
className={clsx(
|
||||
'px-2 py-1 rounded text-xs font-medium transition-colors min-h-touch',
|
||||
viewMode === opt.value
|
||||
? 'bg-white text-primary-600 shadow-sm'
|
||||
: 'text-secondary-500 hover:text-secondary-700',
|
||||
)}
|
||||
aria-label={opt.label}
|
||||
aria-pressed={viewMode === opt.value}
|
||||
>
|
||||
{opt.value === 'list' && (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
)}
|
||||
{opt.value === 'table' && (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4h4v4H4V4zm6 0h4v4h-4V4zm6 0h4v4h-4V4zM4 10h4v4H4v-4zm6 0h4v4h-4v-4zm6 0h4v4h-4v-4zM4 16h4v4H4v-4zm6 0h4v4h-4v-4zm6 0h4v4h-4v-4z" />
|
||||
</svg>
|
||||
)}
|
||||
{opt.value === 'cards' && (
|
||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 5h6v6H4V5zm10 0h6v6h-6V5zM4 13h6v6H4v-6zm10 0h6v6h-6v-6z" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* New contact */}
|
||||
<button
|
||||
onClick={handleCreate}
|
||||
className="p-1.5 rounded-md hover:bg-secondary-100 min-h-touch min-w-touch flex items-center justify-center"
|
||||
aria-label={t('contacts.create')}
|
||||
title={t('contacts.create')}
|
||||
data-testid="contacts-new-btn"
|
||||
>
|
||||
<svg className="w-4 h-4 text-primary-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
|
||||
Reference in New Issue
Block a user