INSIGHT-MVP/packages/frontend/src/crm/hooks.ts
Thomas Reitz 2381409e6d feat(frontend): add Lexware Office integration UI
Implements complete Lexware Office frontend integration:
- Types: LexwareVoucher, DealVoucher, LexwareContact, VoucherType
- API: lexwareContactsApi + lexwareVouchersApi (16 endpoints)
- Hooks: React Query hooks for search, link/unlink, sync, vouchers
- LexwareSection: reusable component for Company/Contact detail pages
  with status badge, sync/push/refresh buttons, and voucher table
- LexwareSearchModal: search Lexware contacts and link to CRM entities
- DealVouchersSection: link/unlink vouchers on Deal detail page
- CRM Settings: Lexware Office toggle (admin-configurable)
- Company/Contact/Deal detail pages extended with Lexware sections

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 22:20:18 +01:00

568 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// ============================================================
// CRM React Query Hooks
// ============================================================
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
contactsApi,
dealsApi,
pipelinesApi,
activitiesApi,
companiesApi,
lexwareContactsApi,
lexwareVouchersApi,
} from './api';
import type {
ContactsQueryParams,
DealsQueryParams,
ActivitiesQueryParams,
CreateContactPayload,
UpdateContactPayload,
CreateDealPayload,
UpdateDealPayload,
CreatePipelinePayload,
UpdatePipelinePayload,
CreateStagePayload,
UpdateStagePayload,
CreateActivityPayload,
UpdateActivityPayload,
CompaniesQueryParams,
CreateCompanyPayload,
UpdateCompanyPayload,
LexwareContactSearchParams,
LexwareVouchersQueryParams,
} from './types';
// --- Query Key Factory ---
export const crmKeys = {
contacts: {
all: ['crm', 'contacts'] as const,
list: (params: ContactsQueryParams) =>
['crm', 'contacts', 'list', params] as const,
detail: (id: string) => ['crm', 'contacts', 'detail', id] as const,
},
deals: {
all: ['crm', 'deals'] as const,
list: (params: DealsQueryParams) =>
['crm', 'deals', 'list', params] as const,
detail: (id: string) => ['crm', 'deals', 'detail', id] as const,
},
pipelines: {
all: ['crm', 'pipelines'] as const,
list: () => ['crm', 'pipelines', 'list'] as const,
detail: (id: string) => ['crm', 'pipelines', 'detail', id] as const,
},
activities: {
all: ['crm', 'activities'] as const,
list: (params: ActivitiesQueryParams) =>
['crm', 'activities', 'list', params] as const,
detail: (id: string) => ['crm', 'activities', 'detail', id] as const,
},
companies: {
all: ['crm', 'companies'] as const,
list: (params: CompaniesQueryParams) =>
['crm', 'companies', 'list', params] as const,
detail: (id: string) => ['crm', 'companies', 'detail', id] as const,
},
lexware: {
all: ['crm', 'lexware'] as const,
contactSearch: (params: LexwareContactSearchParams) =>
['crm', 'lexware', 'contacts', 'search', params] as const,
vouchersCompany: (companyId: string, params?: LexwareVouchersQueryParams) =>
['crm', 'lexware', 'vouchers', 'company', companyId, params] as const,
vouchersContact: (contactId: string, params?: LexwareVouchersQueryParams) =>
['crm', 'lexware', 'vouchers', 'contact', contactId, params] as const,
vouchersDeal: (dealId: string) =>
['crm', 'lexware', 'vouchers', 'deal', dealId] as const,
},
};
// ============================================================
// Contacts
// ============================================================
export function useContacts(params: ContactsQueryParams) {
return useQuery({
queryKey: crmKeys.contacts.list(params),
queryFn: () => contactsApi.list(params),
});
}
export function useContact(id: string) {
return useQuery({
queryKey: crmKeys.contacts.detail(id),
queryFn: () => contactsApi.getById(id),
enabled: !!id,
});
}
export function useCreateContact() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateContactPayload) => contactsApi.create(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useUpdateContact() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateContactPayload }) =>
contactsApi.update(id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useDeleteContact() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => contactsApi.delete(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
// ============================================================
// Deals
// ============================================================
export function useDeals(params: DealsQueryParams) {
return useQuery({
queryKey: crmKeys.deals.list(params),
queryFn: () => dealsApi.list(params),
});
}
export function useDeal(id: string) {
return useQuery({
queryKey: crmKeys.deals.detail(id),
queryFn: () => dealsApi.getById(id),
enabled: !!id,
});
}
export function useCreateDeal() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateDealPayload) => dealsApi.create(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.deals.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useUpdateDeal() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateDealPayload }) =>
dealsApi.update(id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.deals.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useDeleteDeal() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => dealsApi.delete(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.deals.all });
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
// ============================================================
// Pipelines
// ============================================================
export function usePipelines() {
return useQuery({
queryKey: crmKeys.pipelines.list(),
queryFn: () => pipelinesApi.list(),
staleTime: 10 * 60 * 1000, // Pipelines aendern sich selten
});
}
export function usePipeline(id: string) {
return useQuery({
queryKey: crmKeys.pipelines.detail(id),
queryFn: () => pipelinesApi.getById(id),
enabled: !!id,
});
}
export function useCreatePipeline() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreatePipelinePayload) => pipelinesApi.create(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
export function useUpdatePipeline() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
id,
data,
}: {
id: string;
data: UpdatePipelinePayload;
}) => pipelinesApi.update(id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
export function useDeletePipeline() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => pipelinesApi.delete(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
export function useUpdateStage() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
pipelineId,
stageId,
data,
}: {
pipelineId: string;
stageId: string;
data: UpdateStagePayload;
}) => pipelinesApi.updateStage(pipelineId, stageId, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
export function useAddStage() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
pipelineId,
data,
}: {
pipelineId: string;
data: CreateStagePayload;
}) => pipelinesApi.addStage(pipelineId, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
export function useRemoveStage() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
pipelineId,
stageId,
}: {
pipelineId: string;
stageId: string;
}) => pipelinesApi.removeStage(pipelineId, stageId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.pipelines.all });
},
});
}
// ============================================================
// Activities
// ============================================================
export function useActivities(params: ActivitiesQueryParams) {
return useQuery({
queryKey: crmKeys.activities.list(params),
queryFn: () => activitiesApi.list(params),
});
}
export function useCreateActivity() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateActivityPayload) => activitiesApi.create(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.activities.all });
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
},
});
}
export function useUpdateActivity() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
id,
data,
}: {
id: string;
data: UpdateActivityPayload;
}) => activitiesApi.update(id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.activities.all });
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
},
});
}
export function useDeleteActivity() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => activitiesApi.delete(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.activities.all });
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
},
});
}
// ============================================================
// Companies
// ============================================================
export function useCompanies(params: CompaniesQueryParams) {
return useQuery({
queryKey: crmKeys.companies.list(params),
queryFn: () => companiesApi.list(params),
});
}
export function useCompany(id: string) {
return useQuery({
queryKey: crmKeys.companies.detail(id),
queryFn: () => companiesApi.getById(id),
enabled: !!id,
});
}
export function useCreateCompany() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: CreateCompanyPayload) => companiesApi.create(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useUpdateCompany() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ id, data }: { id: string; data: UpdateCompanyPayload }) =>
companiesApi.update(id, data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
export function useDeleteCompany() {
const qc = useQueryClient();
return useMutation({
mutationFn: (id: string) => companiesApi.delete(id),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
},
});
}
// ============================================================
// Lexware Office — Contacts
// ============================================================
export function useLexwareContactSearch(
params: LexwareContactSearchParams,
enabled = true,
) {
return useQuery({
queryKey: crmKeys.lexware.contactSearch(params),
queryFn: () => lexwareContactsApi.search(params),
enabled: enabled && !!(params.name || params.email),
});
}
export function useLinkLexwareCompany() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: { lexwareContactId: string; companyId: string }) =>
lexwareContactsApi.linkCompany(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}
export function useLinkLexwareContact() {
const qc = useQueryClient();
return useMutation({
mutationFn: (data: { lexwareContactId: string; contactId: string }) =>
lexwareContactsApi.linkContact(data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}
export function useUnlinkLexwareCompany() {
const qc = useQueryClient();
return useMutation({
mutationFn: (companyId: string) =>
lexwareContactsApi.unlinkCompany(companyId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}
export function useUnlinkLexwareContact() {
const qc = useQueryClient();
return useMutation({
mutationFn: (contactId: string) =>
lexwareContactsApi.unlinkContact(contactId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}
export function usePushToLexware() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
entityType,
entityId,
}: {
entityType: 'company' | 'contact';
entityId: string;
}) => lexwareContactsApi.push(entityType, entityId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
},
});
}
export function useSyncFromLexware() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({
entityType,
entityId,
}: {
entityType: 'company' | 'contact';
entityId: string;
}) => lexwareContactsApi.sync(entityType, entityId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.companies.all });
qc.invalidateQueries({ queryKey: crmKeys.contacts.all });
},
});
}
// ============================================================
// Lexware Office — Vouchers
// ============================================================
export function useCompanyVouchers(
companyId: string,
params?: LexwareVouchersQueryParams,
) {
return useQuery({
queryKey: crmKeys.lexware.vouchersCompany(companyId, params),
queryFn: () => lexwareVouchersApi.getForCompany(companyId, params),
enabled: !!companyId,
});
}
export function useContactVouchers(
contactId: string,
params?: LexwareVouchersQueryParams,
) {
return useQuery({
queryKey: crmKeys.lexware.vouchersContact(contactId, params),
queryFn: () => lexwareVouchersApi.getForContact(contactId, params),
enabled: !!contactId,
});
}
export function useDealVouchers(dealId: string) {
return useQuery({
queryKey: crmKeys.lexware.vouchersDeal(dealId),
queryFn: () => lexwareVouchersApi.getForDeal(dealId),
enabled: !!dealId,
});
}
export function useLinkVoucherToDeal() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ dealId, voucherId }: { dealId: string; voucherId: string }) =>
lexwareVouchersApi.linkToDeal(dealId, voucherId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
qc.invalidateQueries({ queryKey: crmKeys.deals.all });
},
});
}
export function useUnlinkVoucherFromDeal() {
const qc = useQueryClient();
return useMutation({
mutationFn: ({ dealId, voucherId }: { dealId: string; voucherId: string }) =>
lexwareVouchersApi.unlinkFromDeal(dealId, voucherId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
qc.invalidateQueries({ queryKey: crmKeys.deals.all });
},
});
}
export function useRefreshCompanyVouchers() {
const qc = useQueryClient();
return useMutation({
mutationFn: (companyId: string) =>
lexwareVouchersApi.refreshCompany(companyId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}
export function useRefreshContactVouchers() {
const qc = useQueryClient();
return useMutation({
mutationFn: (contactId: string) =>
lexwareVouchersApi.refreshContact(contactId),
onSuccess: () => {
qc.invalidateQueries({ queryKey: crmKeys.lexware.all });
},
});
}