Files
leocrm/frontend/src/api/hooks.ts
T

1243 lines
36 KiB
TypeScript
Raw Normal View History

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<T> {
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<string, any>;
field_permissions?: Record<string, any>;
}
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<any>('/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<PaginatedResponse<any>>(`/users?page=${page}&page_size=${pageSize}`),
});
}
export function useUser(id?: string) {
return useQuery({
queryKey: ['users', id],
queryFn: () => apiGet<any>(`/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<PaginatedResponse<Company>>(`/companies?${params.toString()}`),
});
}
export function useCompany(id?: string) {
return useQuery({
queryKey: ['companies', id],
queryFn: () => apiGet<CompanyDetail>(`/companies/${id}`),
enabled: !!id,
});
}
export function useCreateCompany() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Partial<Company>) => apiPost('/companies', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['companies'] });
},
});
}
export function useUpdateCompany() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Company> }) =>
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<PaginatedResponse<Contact>>(`/contacts?${params.toString()}`),
});
}
export function useContact(id?: string) {
return useQuery({
queryKey: ['contacts', id],
queryFn: () => apiGet<ContactDetail>(`/contacts/${id}`),
enabled: !!id,
});
}
export function useCreateContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Partial<Contact> & { 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<Contact> & { 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<PaginatedResponse<AuditLogEntry>>(`/audit-log?${params.toString()}`),
retry: false,
});
}
export function useGlobalSearch(query: string, entityTypes?: string[]) {
return useQuery({
queryKey: ['globalSearch', query, entityTypes],
queryFn: async (): Promise<SearchResult[]> => {
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<PaginatedResponse<any>>('/notifications'),
});
}
export function useUnreadNotificationCount() {
return useQuery({
queryKey: ['notifications', 'unread-count'],
queryFn: () => apiGet<number>('/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<any>('/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<any>('/roles');
return data;
},
});
}
export function usePermissions() {
return useQuery({
queryKey: ['permissions'],
queryFn: () => apiGet<PermissionsResponse>('/roles/permissions'),
});
}
export function useCreateRole() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: { name: string; permissions: Record<string, any>; field_permissions: Record<string, any> }) =>
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<string, any>; field_permissions?: Record<string, any> } }) =>
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<string, any> | 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<string, any> | 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<PaginatedResponse<UnifiedContact>>(`/contacts?${params.toString()}`),
});
}
export function useUnifiedContact(id?: string) {
return useQuery({
queryKey: ['unifiedContacts', id],
queryFn: () => apiGet<UnifiedContact & { contact_persons: ContactPerson[] }>(`/contacts/${id}`),
enabled: !!id,
});
}
export function useCreateUnifiedContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Partial<UnifiedContact>) => apiPost('/contacts', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
},
});
}
export function useUpdateUnifiedContact() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<UnifiedContact> }) =>
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<ContactPerson> }) =>
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<ContactPerson> }) =>
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<PaginatedResponse<UnifiedContact>> {
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<PaginatedResponse<UnifiedContact>>(`/contacts?${params.toString()}`);
}
export async function fetchContact(id: string): Promise<UnifiedContact & { contact_persons: ContactPerson[] }> {
return apiGet<UnifiedContact & { contact_persons: ContactPerson[] }>(`/contacts/${id}`);
}
export async function createContact(data: Partial<UnifiedContact>): Promise<UnifiedContact> {
return apiPost<UnifiedContact>('/contacts', data);
}
export async function updateContact(id: string, data: Partial<UnifiedContact>): Promise<UnifiedContact> {
return apiPut<UnifiedContact>(`/contacts/${id}`, data);
}
export async function deleteContact(id: string, hard?: boolean): Promise<void> {
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<ContactPerson>): Promise<ContactPerson> {
return apiPost<ContactPerson>(`/contacts/${contactId}/persons`, data);
}
export async function updateContactPerson(contactId: string, personId: string, data: Partial<ContactPerson>): Promise<ContactPerson> {
return apiPut<ContactPerson>(`/contacts/${contactId}/persons/${personId}`, data);
}
export async function deleteContactPerson(contactId: string, personId: string): Promise<void> {
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<SystemSettings>('/system-settings'),
});
}
export function useUpdateSystemSettings() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (data: Partial<SystemSettings>) => 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<Currency>) => apiPost('/currencies', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['currencies'] });
},
});
}
export function useUpdateCurrency() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Currency> }) =>
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<TaxRate>) => apiPost('/taxes', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['taxes'] });
},
});
}
export function useUpdateTax() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<TaxRate> }) =>
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<Sequence>) => apiPost('/sequences', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['sequences'] });
},
});
}
export function useUpdateSequence() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Sequence> }) =>
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<Address> & { 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<Address> }) =>
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<string, any>;
denied_permissions: string[];
field_permissions: Record<string, any>;
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<Group>) => apiPost('/groups', data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['groups'] });
},
});
}
export function useUpdateGroup() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<Group> }) =>
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,
});
}