Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new ContactAvatar and ContactAvatarPicker components for enhanced avatar management in contact views. - Updated ContactDetailView and ContactFormView to utilize the new avatar components, improving user experience when adding or editing contacts. - Enhanced ContactHoverCard and ContactRow components to display avatars, providing a more visually appealing interface. - Added loading and error states in ContactsListView for better user feedback during data fetching. - Implemented a new ContactsLoadState component to handle loading and error scenarios in the contacts list. - Updated package.json to include @formkit/auto-animate for improved UI animations.
196 lines
5.7 KiB
TypeScript
196 lines
5.7 KiB
TypeScript
"use client"
|
|
|
|
import { useCallback, useEffect, useState } from "react"
|
|
import { cn } from "@/lib/utils"
|
|
import { useIsMobile } from "@/hooks/use-mobile"
|
|
import { ContactsSidebar } from "./contacts-sidebar"
|
|
import { ContactsHeader } from "./contacts-header"
|
|
import { ContactsTable } from "./contacts-table"
|
|
import { ContactDetailPage } from "./contact-detail-page"
|
|
import { ContactCreatePage } from "./contact-create-page"
|
|
import { MergeDuplicatesView } from "./merge-duplicates-view"
|
|
import { OtherContactsView } from "./other-contacts-view"
|
|
import { IgnoredContactsView } from "./ignored-contacts-view"
|
|
import { BlockedContactsView } from "./blocked-contacts-view"
|
|
import { TrashView } from "./trash-view"
|
|
import { BulkCreateDialog } from "./bulk-create-dialog"
|
|
import { ImportDialog } from "./import-dialog"
|
|
import { CONTACTS_SHELL_CLASS } from "@/lib/contacts-chrome-classes"
|
|
|
|
export type ContactsPageView =
|
|
| "contacts"
|
|
| "frequent"
|
|
| "other"
|
|
| "ignored"
|
|
| "blocked"
|
|
| "merge"
|
|
| "import"
|
|
| "trash"
|
|
| "detail"
|
|
| "create"
|
|
| "edit"
|
|
| "label"
|
|
|
|
export function ContactsAppShell() {
|
|
const isMobile = useIsMobile()
|
|
const [currentView, setCurrentView] = useState<ContactsPageView>("contacts")
|
|
const [activeContactId, setActiveContactId] = useState<string | null>(null)
|
|
const [activeLabelId, setActiveLabelId] = useState<string | null>(null)
|
|
const [searchQuery, setSearchQuery] = useState("")
|
|
const [importOpen, setImportOpen] = useState(false)
|
|
const [bulkCreateOpen, setBulkCreateOpen] = useState(false)
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
|
|
useEffect(() => {
|
|
setSearchQuery("")
|
|
}, [currentView, activeLabelId])
|
|
|
|
useEffect(() => {
|
|
if (isMobile) {
|
|
setSidebarOpen(false)
|
|
} else {
|
|
setSidebarOpen(true)
|
|
}
|
|
}, [isMobile])
|
|
|
|
const closeSidebar = useCallback(() => setSidebarOpen(false), [])
|
|
const openSidebar = useCallback(() => setSidebarOpen(true), [])
|
|
const toggleSidebar = useCallback(() => setSidebarOpen((open) => !open), [])
|
|
|
|
function openContact(id: string) {
|
|
setActiveContactId(id)
|
|
setCurrentView("detail")
|
|
}
|
|
|
|
function openCreate() {
|
|
setActiveContactId(null)
|
|
setCurrentView("create")
|
|
}
|
|
|
|
function openEdit(id: string) {
|
|
setActiveContactId(id)
|
|
setCurrentView("edit")
|
|
}
|
|
|
|
function goBack() {
|
|
setActiveContactId(null)
|
|
setCurrentView("contacts")
|
|
}
|
|
|
|
function goToContactsList() {
|
|
setActiveContactId(null)
|
|
setActiveLabelId(null)
|
|
setSearchQuery("")
|
|
setCurrentView("contacts")
|
|
if (isMobile) closeSidebar()
|
|
}
|
|
|
|
function handleNavigate(view: ContactsPageView) {
|
|
if (view === "import") {
|
|
setImportOpen(true)
|
|
if (isMobile) closeSidebar()
|
|
return
|
|
}
|
|
setCurrentView(view)
|
|
if (isMobile) closeSidebar()
|
|
}
|
|
|
|
function handleSelectLabel(id: string) {
|
|
setActiveLabelId(id)
|
|
setCurrentView("label")
|
|
if (isMobile) closeSidebar()
|
|
}
|
|
|
|
function handleCreateContact() {
|
|
openCreate()
|
|
if (isMobile) closeSidebar()
|
|
}
|
|
|
|
return (
|
|
<div
|
|
data-contacts-panel
|
|
className={cn("relative flex h-dvh max-h-dvh overflow-hidden", CONTACTS_SHELL_CLASS)}
|
|
>
|
|
{isMobile && sidebarOpen && (
|
|
<button
|
|
type="button"
|
|
aria-label="Fermer le menu"
|
|
className="absolute inset-0 z-40 bg-black/20"
|
|
onClick={closeSidebar}
|
|
/>
|
|
)}
|
|
|
|
<ContactsSidebar
|
|
open={sidebarOpen}
|
|
overlay={isMobile}
|
|
onToggle={toggleSidebar}
|
|
onClose={closeSidebar}
|
|
currentView={currentView}
|
|
activeLabelId={activeLabelId}
|
|
onNavigate={handleNavigate}
|
|
onHome={goToContactsList}
|
|
onCreateContact={handleCreateContact}
|
|
onBulkCreate={() => setBulkCreateOpen(true)}
|
|
onSelectLabel={handleSelectLabel}
|
|
/>
|
|
|
|
<div className="flex min-w-0 flex-1 flex-col">
|
|
<ContactsHeader
|
|
searchQuery={searchQuery}
|
|
onSearchChange={setSearchQuery}
|
|
sidebarOpen={sidebarOpen}
|
|
onOpenSidebar={openSidebar}
|
|
/>
|
|
<main className="min-h-0 flex-1 overflow-y-auto">
|
|
{(currentView === "contacts" ||
|
|
currentView === "frequent" ||
|
|
currentView === "label") && (
|
|
<ContactsTable
|
|
view={currentView}
|
|
searchQuery={searchQuery}
|
|
activeLabelId={activeLabelId}
|
|
onOpenContact={openContact}
|
|
/>
|
|
)}
|
|
{currentView === "other" && (
|
|
<OtherContactsView searchQuery={searchQuery} />
|
|
)}
|
|
{currentView === "ignored" && (
|
|
<IgnoredContactsView searchQuery={searchQuery} />
|
|
)}
|
|
{currentView === "blocked" && (
|
|
<BlockedContactsView searchQuery={searchQuery} />
|
|
)}
|
|
{currentView === "detail" && activeContactId && (
|
|
<ContactDetailPage
|
|
contactId={activeContactId}
|
|
onBack={goBack}
|
|
onEdit={openEdit}
|
|
/>
|
|
)}
|
|
{currentView === "create" && (
|
|
<ContactCreatePage
|
|
mode="create"
|
|
onBack={goBack}
|
|
onSaved={(id) => openContact(id)}
|
|
/>
|
|
)}
|
|
{currentView === "edit" && activeContactId && (
|
|
<ContactCreatePage
|
|
mode="edit"
|
|
contactId={activeContactId}
|
|
onBack={() => openContact(activeContactId)}
|
|
onSaved={(id) => openContact(id)}
|
|
/>
|
|
)}
|
|
{currentView === "merge" && <MergeDuplicatesView />}
|
|
{currentView === "trash" && <TrashView />}
|
|
</main>
|
|
</div>
|
|
|
|
<ImportDialog open={importOpen} onOpenChange={setImportOpen} />
|
|
<BulkCreateDialog open={bulkCreateOpen} onOpenChange={setBulkCreateOpen} onOpenImport={() => setImportOpen(true)} />
|
|
</div>
|
|
)
|
|
}
|