Phase 1C: Frontend unified contact UI
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Contact Detail Page — loads a single contact by ID from route params.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ContactDetail } from '@/components/contacts/ContactDetail';
|
||||
import { ContactEditModal } from '@/components/contacts/ContactEditModal';
|
||||
import { useUnifiedContact, type UnifiedContact } from '@/api/hooks';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { ChevronLeft } from 'lucide-react';
|
||||
|
||||
export function ContactDetailPage() {
|
||||
const { t } = useTranslation();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const navigate = useNavigate();
|
||||
const { data: contact, isLoading } = useUnifiedContact(id);
|
||||
const [editModalOpen, setEditModalOpen] = React.useState(false);
|
||||
const [editingContact, setEditingContact] = React.useState<UnifiedContact | null>(null);
|
||||
|
||||
const handleEdit = () => {
|
||||
if (contact) {
|
||||
setEditingContact(contact);
|
||||
setEditModalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleted = () => {
|
||||
navigate('/contacts');
|
||||
};
|
||||
|
||||
const handleSaved = () => {
|
||||
setEditModalOpen(false);
|
||||
setEditingContact(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full" data-testid="contact-detail-page">
|
||||
<div className="flex items-center gap-2 px-4 py-2 border-b border-secondary-200 bg-white">
|
||||
<button
|
||||
onClick={() => navigate('/contacts')}
|
||||
className="inline-flex items-center gap-1 px-2 py-1 rounded text-sm text-secondary-700 hover:bg-secondary-100 min-h-touch"
|
||||
aria-label={t('common.back')}
|
||||
>
|
||||
<ChevronLeft className="w-4 h-4" aria-hidden="true" strokeWidth={2} />
|
||||
<span>{t('contacts.title')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex-1 overflow-y-auto">
|
||||
<ContactDetail
|
||||
contact={contact ?? null}
|
||||
loading={isLoading}
|
||||
onEdit={handleEdit}
|
||||
onDeleted={handleDeleted}
|
||||
/>
|
||||
</div>
|
||||
<ContactEditModal
|
||||
open={editModalOpen}
|
||||
onClose={() => setEditModalOpen(false)}
|
||||
contact={editingContact}
|
||||
onSaved={handleSaved}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -264,6 +264,30 @@ export function ContactsListPage() {
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Type filter toggle */}
|
||||
<div className="flex items-center gap-0.5 bg-secondary-100 rounded-md p-0.5 mr-1">
|
||||
{[
|
||||
{ value: 'all' as ContactFilter, label: t('common.all') },
|
||||
{ value: 'company' as ContactFilter, label: t('contacts.companies') },
|
||||
{ value: 'person' as ContactFilter, label: t('contacts.persons') },
|
||||
].map((opt) => (
|
||||
<button
|
||||
key={opt.value}
|
||||
onClick={() => handleSelectFilter(opt.value)}
|
||||
className={clsx(
|
||||
'px-2 py-1 rounded text-xs font-medium transition-colors min-h-touch',
|
||||
selectedFilter === opt.value
|
||||
? 'bg-white text-primary-600 shadow-sm'
|
||||
: 'text-secondary-500 hover:text-secondary-700',
|
||||
)}
|
||||
aria-label={opt.label}
|
||||
aria-pressed={selectedFilter === opt.value}
|
||||
>
|
||||
{opt.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* View mode toggle */}
|
||||
<div className="flex items-center gap-0.5 bg-secondary-100 rounded-md p-0.5">
|
||||
{viewModeOptions.map((opt) => (
|
||||
|
||||
Reference in New Issue
Block a user