Phase 0.4: split hooks.ts into separate API modules
This commit is contained in:
+1
-1
@@ -12,7 +12,7 @@
|
|||||||
| 0.1 | ✅ done | 2026-07-23 | codebase-vs-requirements.md neu geschrieben, security-review-phase2.md Resolution Summary, architecture.md Implementation Status, MASTER-PLAN.md + PROGRESS.md erstellt |
|
| 0.1 | ✅ done | 2026-07-23 | codebase-vs-requirements.md neu geschrieben, security-review-phase2.md Resolution Summary, architecture.md Implementation Status, MASTER-PLAN.md + PROGRESS.md erstellt |
|
||||||
| 0.2 | ✅ done | 2026-07-23 | lucide-react installieren + Icons migrieren |
|
| 0.2 | ✅ done | 2026-07-23 | lucide-react installieren + Icons migrieren |
|
||||||
| 0.3 | ✅ done | 2026-07-23 | date-fns installieren + Datum-Formatierung |
|
| 0.3 | ✅ done | 2026-07-23 | date-fns installieren + Datum-Formatierung |
|
||||||
| 0.4 | ⬜ pending | | hooks.ts aufteilen |
|
| 0.4 | ✅ done | 2026-07-23 | hooks.ts aufteilen — 1298 Zeilen → 12 Module + Re-Export-Hub |
|
||||||
| 0.5 | ⬜ pending | | Store-Verzeichnis konsolidieren |
|
| 0.5 | ⬜ pending | | Store-Verzeichnis konsolidieren |
|
||||||
| 0.6 | ⬜ pending | | Frontend-Bestandsanalyse als Dokument speichern |
|
| 0.6 | ⬜ pending | | Frontend-Bestandsanalyse als Dokument speichern |
|
||||||
| 0.7 | ⬜ pending | | UI-Design-Richtlinien erstellen |
|
| 0.7 | ⬜ pending | | UI-Design-Richtlinien erstellen |
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
/**
|
||||||
|
* Attachment and address hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete, apiClient } from './client';
|
||||||
|
|
||||||
|
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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attachments
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Addresses
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* Audit log hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { apiGet } from './client';
|
||||||
|
import { PaginatedResponse } from './types';
|
||||||
|
|
||||||
|
export interface AuditLogEntry {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
user: string;
|
||||||
|
action: string;
|
||||||
|
entity: string;
|
||||||
|
entity_id?: string;
|
||||||
|
details?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/**
|
||||||
|
* Authentication hooks: login, logout, current user, password reset, tenant switching.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiPost, apiGet, 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 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();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* Contact CRUD hooks (legacy contact model — not unified contacts).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete, apiPut } from './client';
|
||||||
|
import { PaginatedResponse, Contact, ContactDetail } from './types';
|
||||||
|
import { ContactFolder } from './contactFolders';
|
||||||
|
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Contact Folders ──
|
||||||
|
|
||||||
|
export function useContactFolders() {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['contactFolders'],
|
||||||
|
queryFn: () => apiGet<ContactFolder[]>('/contact-folders'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCreateContactFolder() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (data: { name: string; parent_id?: string }) => apiPost('/contact-folders', data),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdateContactFolder() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ id, data }: { id: string; data: { name?: string; parent_id?: string | null; sort_order?: number } }) =>
|
||||||
|
apiPut(`/contact-folders/${id}`, data),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDeleteContactFolder() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (id: string) => apiDelete(`/contact-folders/${id}`),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMoveContactToFolder() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: ({ contactId, folderId }: { contactId: string; folderId: string | null }) =>
|
||||||
|
apiPut(`/contact-folders/contacts/${contactId}/move`, { folder_id: folderId }),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['unifiedContacts'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Group and group member hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete } from './client';
|
||||||
|
|
||||||
|
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,
|
||||||
|
});
|
||||||
|
}
|
||||||
+35
-1197
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
|||||||
|
/**
|
||||||
|
* Notification hooks: list, unread count, mark read, delete, types, preferences.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPatch, apiDelete } from './client';
|
||||||
|
import { PaginatedResponse } from './types';
|
||||||
|
|
||||||
|
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 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 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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
* Plugin management hooks: list, install, activate, deactivate, uninstall.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiDelete } from './client';
|
||||||
|
|
||||||
|
export interface Plugin {
|
||||||
|
name: string;
|
||||||
|
display_name?: string;
|
||||||
|
description?: string;
|
||||||
|
version?: string;
|
||||||
|
status: 'discovered' | 'installed' | 'active' | 'inactive';
|
||||||
|
installed?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Role and permission hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete } from './client';
|
||||||
|
|
||||||
|
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 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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* System settings, currency, tax, and sequence hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete, apiClient } from './client';
|
||||||
|
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Shared type definitions used across multiple API modules.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,268 @@
|
|||||||
|
/**
|
||||||
|
* Unified Contact (Rentman-style) hooks and standalone API functions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPut, apiDelete } from './client';
|
||||||
|
import { PaginatedResponse } from './types';
|
||||||
|
|
||||||
|
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;
|
||||||
|
folder_id?: string | 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,
|
||||||
|
folderId?: 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);
|
||||||
|
if (folderId) params.set('folder_id', folderId);
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ['unifiedContacts', page, pageSize, search, contactType, sortBy, sortOrder, folderId],
|
||||||
|
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}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* User CRUD hooks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { apiGet, apiPost, apiPatch, apiDelete } from './client';
|
||||||
|
import { PaginatedResponse } from './types';
|
||||||
|
|
||||||
|
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'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user