Files
leocrm/frontend/src/api/unifiedContacts.ts
T
Agent Zero abbe7a18fc fix(audit): P0-P3 audit fixes — 838 ruff errors → 0, 30 F821 bugs fixed, 118 files changed
- P0: hooks.py 3-tuple fix, trigger_dispatcher Contract, contacts/plugin unregister_actions_by_owner
- P0: 5 test files — check_permission mocks removed, hardcoded DB credential → env var
- P1: attachment_service DmsFile via Contract helper, restore_registry/history_hooks dedup
- P1: mail/plugin restore unregister, mcp_client datetime.now(UTC), saved_views/filters patterns
- P1: ProtectedRoute fail-closed, 13 test assertion fixes (bcrypt, DB-URLs, SECRET_KEYs)
- P2: deprecated notifications → post_system_message (3 files), forgejo Base, report_generator lazy import
- P2: webhooks permissions, deps.py/roles.py plugin perms removed, import_export default
- P2: address/tags/entity_links patterns removed, worker.py Contract-Umgehungen fixed
- P2: 28 frontend TODOs (hardcoded constants, deprecated notification API)
- P3: dead code, duplicates, deprecated imports, private attr, __import__ inline
- P3: 8 frontend TODOs (LucideIcons, inline styles, XSS, i18n)
- ruff: 838 → 0 (612 auto-fix + 246 manual + 27 F821 regression fix)
- F821: 30 → 0 (AutomationDefinition, DmsFile, user_id, Path, Any, String)
- Contract-Umgehungen: 2 neue gefunden (worker.py:169, worker.py:280) und gefixt
2026-08-16 01:17:18 +02:00

270 lines
9.0 KiB
TypeScript

// TODO: P2-F9 — Consolidate dual API clients
/**
* 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;
suffix?: 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}`);
}