Frontend: unified 3-column contacts page (folder tree | list/table/cards | detail), all Rentman fields, ContactPerson CRUD, removed old Contact/Company pages
This commit is contained in:
+265
-1
@@ -1,5 +1,5 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiPost, apiGet, apiPatch, apiDelete, apiClient, setCsrfToken } from './client';
|
||||
import { apiPost, apiGet, apiPatch, apiPut, apiDelete, apiClient, setCsrfToken } from './client';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
|
||||
export interface LoginPayload {
|
||||
@@ -567,6 +567,270 @@ export function useDeleteRole() {
|
||||
});
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// 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
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user