143 lines
3.9 KiB
TypeScript
143 lines
3.9 KiB
TypeScript
"use client"
|
|
|
|
import { create } from "zustand"
|
|
import { persist } from "zustand/middleware"
|
|
import { debouncedPersistJSONStorage } from "@/lib/stores/debounced-json-storage"
|
|
import { MOCK_FULL_CONTACTS } from "./mock-data"
|
|
import type { FullContact } from "./types"
|
|
|
|
type ContactsView = "list" | "view" | "create" | "edit"
|
|
|
|
/** Prefill for "Nouveau contact" opened from hover card / elsewhere. */
|
|
export type ContactCreateDraft = {
|
|
firstName?: string
|
|
lastName?: string
|
|
emails?: { value: string; label: string }[]
|
|
}
|
|
|
|
interface ContactsState {
|
|
contacts: FullContact[]
|
|
panelOpen: boolean
|
|
view: ContactsView
|
|
activeContactId: string | null
|
|
searchQuery: string
|
|
searchMode: boolean
|
|
createDraft: ContactCreateDraft | null
|
|
}
|
|
|
|
interface ContactsActions {
|
|
togglePanel: () => void
|
|
openPanel: () => void
|
|
closePanel: () => void
|
|
openContactDetail: (contactId: string) => void
|
|
openCreateContact: (draft?: ContactCreateDraft | null) => void
|
|
clearCreateDraft: () => void
|
|
setView: (view: ContactsView, activeContactId?: string | null) => void
|
|
setSearchQuery: (q: string) => void
|
|
setSearchMode: (active: boolean) => void
|
|
addContact: (
|
|
contact: Omit<FullContact, "id" | "createdAt" | "updatedAt">
|
|
) => string
|
|
updateContact: (id: string, patch: Partial<FullContact>) => void
|
|
deleteContact: (id: string) => void
|
|
}
|
|
|
|
export type ContactsStore = ContactsState & ContactsActions
|
|
|
|
export const useContactsStore = create<ContactsStore>()(
|
|
persist(
|
|
(set) => ({
|
|
contacts: MOCK_FULL_CONTACTS,
|
|
panelOpen: false,
|
|
view: "list",
|
|
activeContactId: null,
|
|
searchQuery: "",
|
|
searchMode: false,
|
|
createDraft: null,
|
|
|
|
togglePanel: () =>
|
|
set((s) =>
|
|
s.panelOpen
|
|
? {
|
|
panelOpen: false,
|
|
view: "list",
|
|
activeContactId: null,
|
|
searchQuery: "",
|
|
searchMode: false,
|
|
createDraft: null,
|
|
}
|
|
: { panelOpen: true }
|
|
),
|
|
|
|
openPanel: () => set({ panelOpen: true }),
|
|
|
|
closePanel: () =>
|
|
set({
|
|
panelOpen: false,
|
|
view: "list",
|
|
activeContactId: null,
|
|
searchQuery: "",
|
|
searchMode: false,
|
|
createDraft: null,
|
|
}),
|
|
|
|
openContactDetail: (contactId) =>
|
|
set({
|
|
panelOpen: true,
|
|
view: "view",
|
|
activeContactId: contactId,
|
|
searchQuery: "",
|
|
searchMode: false,
|
|
createDraft: null,
|
|
}),
|
|
|
|
openCreateContact: (draft = null) =>
|
|
set({
|
|
panelOpen: true,
|
|
view: "create",
|
|
activeContactId: null,
|
|
searchQuery: "",
|
|
searchMode: false,
|
|
createDraft: draft,
|
|
}),
|
|
|
|
clearCreateDraft: () => set({ createDraft: null }),
|
|
|
|
setView: (view, activeContactId = null) =>
|
|
set({ view, activeContactId, createDraft: null }),
|
|
|
|
setSearchQuery: (searchQuery) => set({ searchQuery }),
|
|
|
|
setSearchMode: (searchMode) =>
|
|
set(searchMode ? { searchMode } : { searchMode, searchQuery: "" }),
|
|
|
|
addContact: (contact) => {
|
|
const id = `contact-${crypto.randomUUID()}`
|
|
const now = Date.now()
|
|
const full: FullContact = { ...contact, id, createdAt: now, updatedAt: now }
|
|
set((s) => ({ contacts: [...s.contacts, full] }))
|
|
return id
|
|
},
|
|
|
|
updateContact: (id, patch) =>
|
|
set((s) => ({
|
|
contacts: s.contacts.map((c) =>
|
|
c.id === id ? { ...c, ...patch, updatedAt: Date.now() } : c
|
|
),
|
|
})),
|
|
|
|
deleteContact: (id) =>
|
|
set((s) => ({
|
|
contacts: s.contacts.filter((c) => c.id !== id),
|
|
activeContactId: s.activeContactId === id ? null : s.activeContactId,
|
|
view: s.activeContactId === id ? "list" : s.view,
|
|
})),
|
|
}),
|
|
{
|
|
name: "contacts-store",
|
|
storage: debouncedPersistJSONStorage,
|
|
partialize: (state) => ({ contacts: state.contacts }),
|
|
}
|
|
)
|
|
)
|