import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiPost, apiGet, apiPatch, apiPut, apiDelete, apiClient, setCsrfToken } from './client'; import { useAuthStore } from '@/store/authStore'; export interface LoginPayload { email: string; password: string; } export interface PasswordResetRequestPayload { email: string; } export interface PasswordResetConfirmPayload { token: string; password: string; } export interface PaginatedResponse { items: T[]; total: number; page: number; page_size: number; } export interface Company { id: string; name: string; account_number?: string | null; industry?: string | null; phone?: string | null; email?: string | null; website?: string | null; description?: string | null; created_at?: string; updated_at?: string; } export interface CompanyDetail extends Company { contacts?: Contact[]; } export interface Contact { id: string; first_name: string; last_name: string; email: string; phone?: string | null; position?: string | null; company_ids?: string[]; created_at?: string; updated_at?: string; } export interface ContactDetail extends Contact { companies?: Company[]; } export interface AuditLogEntry { id: string; timestamp: string; user: string; action: string; entity: string; entity_id?: string; details?: string; } export interface SearchResult { type: 'company' | 'contact' | 'mail' | 'file' | 'event'; id: string; name: string; description?: string; url: string; } export interface Role { id: string; name: string; permissions: Record; field_permissions?: Record; } export interface PermissionItem { key: string; label: string; category: string; plugin_name?: string; } export interface FieldDefinition { module: string; field: string; label: string; sensitivity: string; } export interface PermissionsResponse { system: PermissionItem[]; plugins: PermissionItem[]; all: PermissionItem[]; field_definitions?: FieldDefinition[]; } export interface Plugin { name: string; display_name?: string; description?: string; version?: string; status: 'discovered' | 'installed' | 'active' | 'inactive'; installed?: boolean; active?: boolean; } export function useLogin() { const { setUser, setError } = useAuthStore(); return useMutation({ mutationFn: (payload: LoginPayload) => apiPost('/auth/login', payload), onSuccess: (data: any) => { setUser(data.user || data); setError(null); // Store CSRF token for subsequent unsafe requests if (data.csrf_token) { setCsrfToken(data.csrf_token); } }, onError: (error: any) => { setError(error.message || 'Login failed'); }, }); } export function useLogout() { const { logout } = useAuthStore(); const queryClient = useQueryClient(); return useMutation({ mutationFn: () => apiPost('/auth/logout'), onSettled: () => { logout(); setCsrfToken(null); queryClient.clear(); }, }); } export function useCurrentUser() { const { setUser, setAuthenticated } = useAuthStore(); return useQuery({ queryKey: ['currentUser'], queryFn: async () => { const data = await apiGet('/auth/me'); setUser(data.user || data); setAuthenticated(true); return data; }, retry: false, staleTime: 5 * 60 * 1000, }); } export function usePasswordResetRequest() { return useMutation({ mutationFn: (payload: PasswordResetRequestPayload) => apiPost('/auth/password-reset/request', payload), }); } export function usePasswordResetConfirm() { return useMutation({ mutationFn: (payload: PasswordResetConfirmPayload) => apiPost('/auth/password-reset/confirm', payload), }); } export function useSwitchTenant() { const { setTenant } = useAuthStore(); const queryClient = useQueryClient(); return useMutation({ mutationFn: (tenantId: string) => apiPost('/auth/switch-tenant', { tenant_id: tenantId }), onSuccess: (data: any) => { setTenant(data.tenant || data); queryClient.invalidateQueries(); }, }); } export function useUsers(page = 1, pageSize = 25) { return useQuery({ queryKey: ['users', page, pageSize], queryFn: () => apiGet>(`/users?page=${page}&page_size=${pageSize}`), }); } export function useUser(id?: string) { return useQuery({ queryKey: ['users', id], queryFn: () => apiGet(`/users/${id}`), enabled: !!id, }); } export function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: any) => apiPost('/users', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } export function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: any }) => apiPatch(`/users/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } export function useDeleteUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/users/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } export function useCompanies(page = 1, pageSize = 25, search?: string, industry?: string, sortBy?: string, sortOrder?: string) { const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) }); if (search) params.set('search', search); if (industry) params.set('industry', industry); if (sortBy) params.set('sort_by', sortBy); if (sortOrder) params.set('sort_order', sortOrder); return useQuery({ queryKey: ['companies', page, pageSize, search, industry, sortBy, sortOrder], queryFn: () => apiGet>(`/companies?${params.toString()}`), }); } export function useCompany(id?: string) { return useQuery({ queryKey: ['companies', id], queryFn: () => apiGet(`/companies/${id}`), enabled: !!id, }); } export function useCreateCompany() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/companies', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['companies'] }); }, }); } export function useUpdateCompany() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPatch(`/companies/${id}`, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['companies'] }); queryClient.invalidateQueries({ queryKey: ['companies', variables.id] }); }, }); } export function useDeleteCompany() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/companies/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['companies'] }); }, }); } export function useCompanyExport(search?: string, industry?: string) { return useMutation({ mutationFn: async () => { const params = new URLSearchParams({ format: 'csv' }); if (search) params.set('search', search); if (industry) params.set('industry', industry); const response = await apiClient.get(`/companies/export?${params.toString()}`, { responseType: 'blob', }); return response.data; }, }); } export function useCompanyImport() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async (file: File) => { const formData = new FormData(); formData.append('file', file); const response = await apiClient.post('/companies/import', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); return response.data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['companies'] }); }, }); } export function useContacts(page = 1, pageSize = 25, search?: string) { const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) }); if (search) params.set('search', search); return useQuery({ queryKey: ['contacts', page, pageSize, search], queryFn: () => apiGet>(`/contacts?${params.toString()}`), }); } export function useContact(id?: string) { return useQuery({ queryKey: ['contacts', id], queryFn: () => apiGet(`/contacts/${id}`), enabled: !!id, }); } export function useCreateContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial & { company_ids?: string[] }) => apiPost('/contacts', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contacts'] }); }, }); } export function useUpdateContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial & { company_ids?: string[] } }) => apiPatch(`/contacts/${id}`, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['contacts'] }); queryClient.invalidateQueries({ queryKey: ['contacts', variables.id] }); }, }); } export function useDeleteContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/contacts/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['contacts'] }); }, }); } export function useAuditLog(page = 1, pageSize = 25, filters?: { user?: string; action?: string; entity?: string; dateFrom?: string; dateTo?: string }) { const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) }); if (filters?.user) params.set('user', filters.user); if (filters?.action) params.set('action', filters.action); if (filters?.entity) params.set('entity', filters.entity); if (filters?.dateFrom) params.set('date_from', filters.dateFrom); if (filters?.dateTo) params.set('date_to', filters.dateTo); return useQuery({ queryKey: ['auditLog', page, pageSize, filters], queryFn: () => apiGet>(`/audit-log?${params.toString()}`), retry: false, }); } export function useGlobalSearch(query: string, entityTypes?: string[]) { return useQuery({ queryKey: ['globalSearch', query, entityTypes], queryFn: async (): Promise => { if (!query.trim()) return []; const { search } = await import('@/api/search'); const response = await search(query, entityTypes, 20); return response.results || []; }, enabled: query.trim().length > 0, staleTime: 30 * 1000, }); } export function useNotifications() { return useQuery({ queryKey: ['notifications'], queryFn: () => apiGet>('/notifications'), }); } export function useUnreadNotificationCount() { return useQuery({ queryKey: ['notifications', 'unread-count'], queryFn: () => apiGet('/notifications/unread-count'), }); } export function useMarkNotificationRead() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiPatch(`/notifications/${id}/read`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); } export function useDeleteNotification() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/notifications/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notifications'] }); }, }); } export interface NotificationTypeItem { type_key: string; plugin_name: string; category: string; label: string; description: string | null; is_enabled_by_default: boolean; is_enabled: boolean; } export function useNotificationTypes() { return useQuery({ queryKey: ['notification-types'], queryFn: () => apiGet<{ items: NotificationTypeItem[] }>('/notifications/types'), }); } export function useNotificationPreferences() { return useQuery({ queryKey: ['notification-preferences'], queryFn: () => apiGet<{ items: { type_key: string; is_enabled: boolean }[] }>('/notifications/preferences'), }); } export function useUpdateNotificationPreference() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ typeKey, isEnabled }: { typeKey: string; isEnabled: boolean }) => apiPatch(`/notifications/preferences/${typeKey}`, { is_enabled: isEnabled }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['notification-preferences'] }); queryClient.invalidateQueries({ queryKey: ['notification-types'] }); }, }); } export function usePlugins() { return useQuery({ queryKey: ['plugins'], queryFn: async () => { const data = await apiGet('/plugins'); return data; }, }); } export function useInstallPlugin() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (name: string) => apiPost(`/plugins/${name}/install`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugins'] }); }, }); } export function useActivatePlugin() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (name: string) => apiPost(`/plugins/${name}/activate`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugins'] }); }, }); } export function useDeactivatePlugin() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (name: string) => apiPost(`/plugins/${name}/deactivate`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugins'] }); }, }); } export function useUninstallPlugin() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ name, removeData }: { name: string; removeData: boolean }) => apiDelete(`/plugins/${name}?remove_data=${removeData}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['plugins'] }); }, }); } export function useRoles() { return useQuery({ queryKey: ['roles'], queryFn: async () => { const data = await apiGet('/roles'); return data; }, }); } export function usePermissions() { return useQuery({ queryKey: ['permissions'], queryFn: () => apiGet('/roles/permissions'), }); } export function useCreateRole() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: { name: string; permissions: Record; field_permissions: Record }) => apiPost('/roles', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roles'] }); }, }); } export function useUpdateRole() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: { name?: string; permissions?: Record; field_permissions?: Record } }) => apiPatch(`/roles/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roles'] }); }, }); } export function useDeleteRole() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/roles/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roles'] }); }, }); } // ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════ // Unified Contact (Rentman-style) hooks // ════════════════════════════════════════════════════════════════════════════ export interface ContactPerson { id: string; contact_id: string; displayname: string; firstname?: string | null; middle_name?: string | null; lastname?: string | null; function?: string | null; phone?: string | null; mobilephone?: string | null; email?: string | null; street?: string | null; number?: string | null; postalcode?: string | null; city?: string | null; state?: string | null; country?: string | null; tags?: string | null; custom?: Record | null; created_at?: string; updated_at?: string; } export interface UnifiedContact { id: string; type: 'company' | 'person'; displayname: string; name?: string | null; firstname?: string | null; surname?: string | null; surfix?: string | null; ext_name_line?: string | null; gender?: string | null; code?: string | null; accounting_code?: string | null; vendor_accounting_code?: string | null; // Mailing address mailing_street?: string | null; mailing_number?: string | null; mailing_unit_number?: string | null; mailing_district?: string | null; mailing_extra_address_line?: string | null; mailing_postalcode?: string | null; mailing_city?: string | null; mailing_state?: string | null; mailing_country?: string | null; // Visit address visit_street?: string | null; visit_number?: string | null; visit_unit_number?: string | null; visit_district?: string | null; visit_extra_address_line?: string | null; visit_postalcode?: string | null; visit_city?: string | null; visit_state?: string | null; // Invoice address invoice_street?: string | null; invoice_number?: string | null; invoice_unit_number?: string | null; invoice_district?: string | null; invoice_extra_address_line?: string | null; invoice_postalcode?: string | null; invoice_city?: string | null; invoice_state?: string | null; invoice_country?: string | null; country?: string | null; // Communication phone_1?: string | null; phone_2?: string | null; email_1?: string | null; email_2?: string | null; website?: string | null; // Financial vat_code?: string | null; fiscal_code?: string | null; commerce_code?: string | null; purchase_number?: string | null; bic?: string | null; bank_account?: string | null; // Discounts discount_crew: number; discount_transport: number; discount_rental: number; discount_sale: number; discount_subrent: number; discount_total: number; // Geo latitude?: number | null; longitude?: number | null; // Notes projectnote?: string | null; projectnote_title?: string | null; contact_warning?: string | null; tags?: string | null; image?: string | null; custom?: Record | null; default_person_id?: string | null; admin_contactperson_id?: string | null; contact_persons?: ContactPerson[]; created_at?: string; updated_at?: string; } export function useUnifiedContacts( page = 1, pageSize = 25, search?: string, contactType?: string, sortBy?: string, sortOrder?: string, ) { const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) }); if (search) params.set('search', search); if (contactType) params.set('type', contactType); if (sortBy) params.set('sort_by', sortBy); if (sortOrder) params.set('sort_order', sortOrder); return useQuery({ queryKey: ['unifiedContacts', page, pageSize, search, contactType, sortBy, sortOrder], queryFn: () => apiGet>(`/contacts?${params.toString()}`), }); } export function useUnifiedContact(id?: string) { return useQuery({ queryKey: ['unifiedContacts', id], queryFn: () => apiGet(`/contacts/${id}`), enabled: !!id, }); } export function useCreateUnifiedContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/contacts', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); }, }); } export function useUpdateUnifiedContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPut(`/contacts/${id}`, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.id] }); }, }); } export function useDeleteUnifiedContact() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, hard }: { id: string; hard?: boolean }) => apiDelete(`/contacts/${id}${hard ? '?hard=true' : ''}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); }, }); } export function useContactPersons(contactId?: string) { return useQuery({ queryKey: ['unifiedContacts', contactId, 'persons'], queryFn: () => apiGet<{ items: ContactPerson[] }>(`/contacts/${contactId}/persons`), enabled: !!contactId, }); } export function useCreateContactPerson() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ contactId, data }: { contactId: string; data: Partial }) => apiPost(`/contacts/${contactId}/persons`, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId] }); queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId, 'persons'] }); }, }); } export function useUpdateContactPerson() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ contactId, personId, data }: { contactId: string; personId: string; data: Partial }) => apiPut(`/contacts/${contactId}/persons/${personId}`, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId] }); queryClient.invalidateQueries({ queryKey: ['unifiedContacts', variables.contactId, 'persons'] }); }, }); } export function useDeleteContactPerson() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ contactId, personId }: { contactId: string; personId: string }) => apiDelete(`/contacts/${contactId}/persons/${personId}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] }); }, }); } // Standalone API functions (for non-hook usage) export async function fetchContacts( page = 1, pageSize = 25, search?: string, contactType?: string, sortBy?: string, sortOrder?: string, ): Promise> { const params = new URLSearchParams({ page: String(page), page_size: String(pageSize) }); if (search) params.set('search', search); if (contactType) params.set('type', contactType); if (sortBy) params.set('sort_by', sortBy); if (sortOrder) params.set('sort_order', sortOrder); return apiGet>(`/contacts?${params.toString()}`); } export async function fetchContact(id: string): Promise { return apiGet(`/contacts/${id}`); } export async function createContact(data: Partial): Promise { return apiPost('/contacts', data); } export async function updateContact(id: string, data: Partial): Promise { return apiPut(`/contacts/${id}`, data); } export async function deleteContact(id: string, hard?: boolean): Promise { await apiDelete(`/contacts/${id}${hard ? '?hard=true' : ''}`); } export async function fetchContactPersons(contactId: string): Promise<{ items: ContactPerson[] }> { return apiGet<{ items: ContactPerson[] }>(`/contacts/${contactId}/persons`); } export async function createContactPerson(contactId: string, data: Partial): Promise { return apiPost(`/contacts/${contactId}/persons`, data); } export async function updateContactPerson(contactId: string, personId: string, data: Partial): Promise { return apiPut(`/contacts/${contactId}/persons/${personId}`, data); } export async function deleteContactPerson(contactId: string, personId: string): Promise { await apiDelete(`/contacts/${contactId}/persons/${personId}`); } // ════════════════════════════════════════════════════════════════════════════ // System Settings, Currency, Tax, Sequence hooks // ════════════════════════════════════════════════════════════════════════════ export interface Currency { id: string; code: string; name: string; symbol: string; is_default: boolean; created_at?: string; updated_at?: string; } export interface TaxRate { id: string; name: string; rate: number; is_default: boolean; country?: string | null; created_at?: string; updated_at?: string; } export interface Sequence { id: string; name: string; prefix: string; next_number: number; padding: number; created_at?: string; updated_at?: string; } export interface SystemSettings { id?: string; company_name: string; company_legal_form?: string | null; company_street: string; company_city: string; company_zip: string; company_country: string; tax_number?: string | null; vat_id?: string | null; iban?: string | null; bic?: string | null; bank_name?: string | null; ceo?: string | null; trade_register?: string | null; default_currency_id?: string | null; default_tax_id?: string | null; invoice_prefix: string; quote_prefix: string; payment_terms_days: number; created_at?: string; updated_at?: string; } // System Settings export function useSystemSettings() { return useQuery({ queryKey: ['systemSettings'], queryFn: () => apiGet('/system-settings'), }); } export function useUpdateSystemSettings() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiClient.put('/system-settings', data).then(r => r.data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['systemSettings'] }); }, }); } // Currencies export function useCurrencies() { return useQuery({ queryKey: ['currencies'], queryFn: () => apiGet<{ items: Currency[]; total: number }>('/currencies'), }); } export function useCreateCurrency() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/currencies', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); }, }); } export function useUpdateCurrency() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPatch(`/currencies/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); }, }); } export function useDeleteCurrency() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/currencies/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['currencies'] }); }, }); } // Taxes export function useTaxes() { return useQuery({ queryKey: ['taxes'], queryFn: () => apiGet<{ items: TaxRate[]; total: number }>('/taxes'), }); } export function useCreateTax() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/taxes', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['taxes'] }); }, }); } export function useUpdateTax() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPatch(`/taxes/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['taxes'] }); }, }); } export function useDeleteTax() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/taxes/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['taxes'] }); }, }); } // Sequences export function useSequences() { return useQuery({ queryKey: ['sequences'], queryFn: () => apiGet<{ items: Sequence[]; total: number }>('/sequences'), }); } export function useCreateSequence() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/sequences', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['sequences'] }); }, }); } export function useUpdateSequence() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPatch(`/sequences/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['sequences'] }); }, }); } export function useDeleteSequence() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/sequences/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['sequences'] }); }, }); } // Attachments export interface Attachment { id: string; entity_type: string; entity_id: string; filename: string; file_path: string; mime_type: string; file_size: number; uploaded_by?: string | null; created_at?: string; updated_at?: string; } export function useAttachments(entityType?: string, entityId?: string) { return useQuery({ queryKey: ['attachments', entityType, entityId], queryFn: () => { const params = new URLSearchParams(); if (entityType) params.set('entity_type', entityType); if (entityId) params.set('entity_id', entityId); return apiGet<{ items: Attachment[]; total: number }>(`/attachments?${params.toString()}`); }, enabled: !!entityType && !!entityId, }); } export function useUploadAttachment() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ file, entityType, entityId }: { file: File; entityType: string; entityId: string }) => { const formData = new FormData(); formData.append('file', file); formData.append('entity_type', entityType); formData.append('entity_id', entityId); const response = await apiClient.post('/attachments', formData, { headers: { 'Content-Type': 'multipart/form-data' }, }); return response.data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['attachments'] }); }, }); } export function useDeleteAttachment() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/attachments/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['attachments'] }); }, }); } // ════════════════════════════════════════════════════════════════════════════ // Address hooks // ════════════════════════════════════════════════════════════════════════════ export interface Address { id: string; entity_type: string; entity_id: string; label: string; address_type: string; street: string | null; street_number: string | null; city: string | null; zip: string | null; state: string | null; country: string | null; is_default: boolean; created_at?: string; updated_at?: string; } export function useAddresses(entityType?: string, entityId?: string) { return useQuery({ queryKey: ['addresses', entityType, entityId], queryFn: () => apiGet<{ items: Address[]; total: number }>( `/addresses?entity_type=${entityType}&entity_id=${entityId}` ), enabled: !!entityType && !!entityId, }); } export function useCreateAddress() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial
& { entity_type: string; entity_id: string }) => apiPost('/addresses', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['addresses'] }); }, }); } export function useUpdateAddress() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial
}) => apiPatch(`/addresses/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['addresses'] }); }, }); } export function useDeleteAddress() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/addresses/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['addresses'] }); }, }); } // ════════════════════════════════════════════════════════════════════════════ // Group hooks // ════════════════════════════════════════════════════════════════════════════ export interface Group { id: string; name: string; description: string | null; permissions: Record; denied_permissions: string[]; field_permissions: Record; permission_version: number; } export interface GroupMember { user_id: string; email: string; name: string; is_active: boolean; } export function useGroups() { return useQuery({ queryKey: ['groups'], queryFn: () => apiGet<{ items: Group[] }>('/groups'), }); } export function useCreateGroup() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: Partial) => apiPost('/groups', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['groups'] }); }, }); } export function useUpdateGroup() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: Partial }) => apiPatch(`/groups/${id}`, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['groups'] }); }, }); } export function useDeleteGroup() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id: string) => apiDelete(`/groups/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['groups'] }); }, }); } export function useGroupMembers(groupId: string | null) { return useQuery({ queryKey: ['groupMembers', groupId], queryFn: () => apiGet<{ items: GroupMember[] }>(`/groups/${groupId}/members`), enabled: !!groupId, }); } export function useAddGroupMember() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ groupId, userId }: { groupId: string; userId: string }) => apiPost(`/groups/${groupId}/members`, { user_id: userId }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['groupMembers'] }); }, }); } export function useRemoveGroupMember() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ groupId, userId }: { groupId: string; userId: string }) => apiDelete(`/groups/${groupId}/members/${userId}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['groupMembers'] }); }, }); } export function useUserGroups(userId: string | null) { return useQuery({ queryKey: ['userGroups', userId], queryFn: () => apiGet<{ items: Group[] }>(`/groups/user/${userId}`), enabled: !!userId, }); }