feat(companies): UI fuer Firmen-Verwaltung mit Ansprechpartner-Links — Modul 12/16 des UI-Backlogs
Check Cross-Plugin Imports / check (push) Has been cancelled
Check Cross-Plugin Imports / check (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Companies API client — CRUD, search, export and contact links for
|
||||
* companies (UI-Backlog module 12/16).
|
||||
*
|
||||
* Backend: /api/v1/companies (contacts plugin). Companies are Contact
|
||||
* entities with type='company'; industry and description live in the
|
||||
* custom JSONB field.
|
||||
* - GET / → paginated list (search, industry, sort)
|
||||
* - POST / → create (201)
|
||||
* - GET /export → CSV/XLSX download
|
||||
* - GET /{id} → detail incl. linked contact persons
|
||||
* - PUT /{id} → update
|
||||
* - DELETE /{id} → soft-delete (?cascade=true: persons too)
|
||||
* - POST /{id}/contacts/{cid} → link a contact person
|
||||
* - DELETE /{id}/contacts/{cid} → unlink (removes all persons links)
|
||||
* - GET /{id}/emails → emails mentioning the company
|
||||
* Permissions: contacts:read (list/get/export) / contacts:write
|
||||
* (create/update/link/unlink) / contacts:delete (delete).
|
||||
*/
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { apiGet, apiPost, apiPut, apiDelete } from '@/api/client';
|
||||
|
||||
export interface Company {
|
||||
id: string;
|
||||
name: string;
|
||||
displayname: string | null;
|
||||
status: string | null;
|
||||
industry: string | null;
|
||||
description: string | null;
|
||||
email_1: string | null;
|
||||
email_2: string | null;
|
||||
phone_1: string | null;
|
||||
phone_2: string | null;
|
||||
website: string | null;
|
||||
mailing_city: string | null;
|
||||
mailing_postalcode: string | null;
|
||||
mailing_country: string | null;
|
||||
tags: string | null;
|
||||
custom: Record<string, unknown> | null;
|
||||
/** Only present in the detail response: */
|
||||
contacts?: CompanyContactPerson[];
|
||||
}
|
||||
|
||||
export interface CompanyContactPerson {
|
||||
id: string;
|
||||
firstname: string | null;
|
||||
lastname: string | null;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
}
|
||||
|
||||
export interface CompanyListResult {
|
||||
items: Company[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
}
|
||||
|
||||
export interface CompanyListParams {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
search?: string | null;
|
||||
industry?: string | null;
|
||||
sort_by?: string;
|
||||
sort_order?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
export interface CompanyCreatePayload {
|
||||
name: string;
|
||||
status?: string;
|
||||
industry?: string;
|
||||
description?: string;
|
||||
email_1?: string;
|
||||
phone_1?: string;
|
||||
website?: string;
|
||||
mailing_city?: string;
|
||||
mailing_country?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export type CompanyUpdatePayload = Partial<CompanyCreatePayload>;
|
||||
|
||||
// ─── Query hooks ─────────────────────────────────────────────
|
||||
|
||||
export function useCompanies(params: CompanyListParams = {}) {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (params.page) searchParams.set('page', String(params.page));
|
||||
if (params.page_size) searchParams.set('page_size', String(params.page_size));
|
||||
if (params.search) searchParams.set('search', params.search);
|
||||
if (params.industry) searchParams.set('industry', params.industry);
|
||||
if (params.sort_by) searchParams.set('sort_by', params.sort_by);
|
||||
if (params.sort_order) searchParams.set('sort_order', params.sort_order);
|
||||
const qs = searchParams.toString();
|
||||
return useQuery<CompanyListResult>({
|
||||
queryKey: ['companies', params.page, params.page_size, params.search, params.industry, params.sort_by, params.sort_order],
|
||||
queryFn: () => apiGet<CompanyListResult>(`/companies${qs ? `?${qs}` : ''}`),
|
||||
});
|
||||
}
|
||||
|
||||
export function useCompany(companyId: string | null) {
|
||||
return useQuery<Company>({
|
||||
queryKey: ['companies', 'detail', companyId],
|
||||
queryFn: () => apiGet<Company>(`/companies/${companyId}`),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCompanyEmails(companyId: string | null) {
|
||||
return useQuery<unknown[]>({
|
||||
queryKey: ['companies', 'emails', companyId],
|
||||
queryFn: () => apiGet<unknown[]>(`/companies/${companyId}/emails`),
|
||||
enabled: !!companyId,
|
||||
});
|
||||
}
|
||||
|
||||
// ─── Mutation hooks ──────────────────────────────────────────
|
||||
|
||||
function invalidateCompanies(qc: ReturnType<typeof useQueryClient>) {
|
||||
qc.invalidateQueries({ queryKey: ['companies'] });
|
||||
}
|
||||
|
||||
export function useCreateCompany() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<Company, Error, CompanyCreatePayload>({
|
||||
mutationFn: (data) => apiPost<Company>('/companies', data),
|
||||
onSuccess: () => invalidateCompanies(qc),
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateCompany() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<Company, Error, { id: string; data: CompanyUpdatePayload }>({
|
||||
mutationFn: ({ id, data }) => apiPut<Company>(`/companies/${id}`, data),
|
||||
onSuccess: (company) => {
|
||||
invalidateCompanies(qc);
|
||||
qc.invalidateQueries({ queryKey: ['companies', 'detail', company.id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteCompany() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, { id: string; cascade?: boolean }>({
|
||||
mutationFn: ({ id, cascade }) =>
|
||||
apiDelete(`/companies/${id}${cascade ? '?cascade=true' : ''}`),
|
||||
onSuccess: () => invalidateCompanies(qc),
|
||||
});
|
||||
}
|
||||
|
||||
export function useLinkContact() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<{ company_id: string; contact_id: string }, Error, { companyId: string; contactId: string }>({
|
||||
mutationFn: ({ companyId, contactId }) =>
|
||||
apiPost(`/companies/${companyId}/contacts/${contactId}`),
|
||||
onSuccess: (r) => {
|
||||
invalidateCompanies(qc);
|
||||
qc.invalidateQueries({ queryKey: ['companies', 'detail', r.company_id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUnlinkContact() {
|
||||
const qc = useQueryClient();
|
||||
return useMutation<void, Error, { companyId: string; contactId: string }>({
|
||||
mutationFn: ({ companyId, contactId }) =>
|
||||
apiDelete(`/companies/${companyId}/contacts/${contactId}`),
|
||||
onSuccess: (_, vars) => {
|
||||
invalidateCompanies(qc);
|
||||
qc.invalidateQueries({ queryKey: ['companies', 'detail', vars.companyId] });
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user