mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 03:26:40 +02:00
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>
568 lines
16 KiB
TypeScript
568 lines
16 KiB
TypeScript
// ============================================================
|
||
// 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 });
|
||
},
|
||
});
|
||
}
|