INSIGHT-MVP/packages/frontend/src/crm/api.ts
Thomas Reitz 2348602fb0 feat: Erweiterte Profilfelder (analog O365) + Profilbild-Sync aus Microsoft 365
Neue Felder im Benutzerprofil (analog Microsoft 365 /me):
- Stellenbezeichnung (jobTitle), Abteilung (department)
- Firma (companyName), Standort (officeLocation)

Changes:
- Core: Prisma-Migration + neue Felder in User-Model, UpdateUserDto,
  findById/update/updateProfile
- CRM: M365UserProfile-Interface + getM365Profile um neue Felder erweitert;
  neue Methode getM365Photo() lädt 96x96 JPEG als Base64 Data-URL;
  neuer Endpoint GET /crm/office365/photo
- Frontend: AuthContext User-Interface, M365UserProfile-Typ, office365Api.getM365Photo()
  ProfilePage: Neues Formular-Fieldset "Organisation" mit 4 Feldern;
  manueller Sync-Button übernimmt auch Profilbild (immer überschreiben);
  useO365ProfileSync: Auto-Sync lädt Foto nur wenn noch kein INSIGHT-Avatar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-13 13:08:56 +01:00

914 lines
25 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 API-Funktionen (nutzt bestehenden Axios-Client)
// ============================================================
import api from '../api/client';
import type {
Contact,
CrmContactLookup,
CreateContactPayload,
UpdateContactPayload,
ContactsQueryParams,
Deal,
CreateDealPayload,
UpdateDealPayload,
DealsQueryParams,
Pipeline,
CreatePipelinePayload,
UpdatePipelinePayload,
CreateStagePayload,
UpdateStagePayload,
PipelineStage,
Activity,
CreateActivityPayload,
UpdateActivityPayload,
ActivitiesQueryParams,
Company,
CreateCompanyPayload,
UpdateCompanyPayload,
CompaniesQueryParams,
Industry,
CreateIndustryPayload,
UpdateIndustryPayload,
AccountType,
CreateAccountTypePayload,
UpdateAccountTypePayload,
RelationshipType,
CreateRelationshipTypePayload,
UpdateRelationshipTypePayload,
CompanyRelationship,
CreateCompanyRelationshipPayload,
TenantUser,
LexwareContact,
LexwareContactSearchParams,
LexwareVoucher,
LexwareVouchersQueryParams,
DealVoucher,
TradeEvent,
CreateTradeEventPayload,
UpdateTradeEventPayload,
EntityOwner,
AddOwnerPayload,
CustomFieldDef,
CustomFieldEntityType,
CreateCustomFieldDefPayload,
UpdateCustomFieldDefPayload,
CustomFieldValue,
SetCustomFieldValuesPayload,
ForecastResponse,
ForecastPeriod,
ImportPreviewResponse,
ImportEntityType,
ImportExecuteRequest,
ImportExecuteResponse,
EnrichmentResponse,
EnrichmentConfig,
Contract,
ContractFile,
CreateContractPayload,
UpdateContractPayload,
ContractsQueryParams,
PaginatedResponse,
SingleResponse,
UserIntegration,
M365Email,
M365CalendarEvent,
M365TaskList,
M365TaskFlat,
CrmOpenTask,
M365Contact,
M365MailFolder,
M365UserProfile,
} from './types';
// --- Contacts ---
export const contactsApi = {
list: (params: ContactsQueryParams) =>
api
.get<PaginatedResponse<Contact>>('/crm/contacts', { params })
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<Contact>>(`/crm/contacts/${id}`)
.then((r) => r.data),
create: (data: CreateContactPayload) =>
api
.post<SingleResponse<Contact>>('/crm/contacts', data)
.then((r) => r.data),
update: (id: string, data: UpdateContactPayload) =>
api
.patch<SingleResponse<Contact>>(`/crm/contacts/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Contact>>(`/crm/contacts/${id}`)
.then((r) => r.data),
lookupByEmail: (email: string) =>
api
.get<SingleResponse<CrmContactLookup>>('/crm/contacts/lookup', { params: { email } })
.then((r) => r.data),
};
// --- Deals ---
export const dealsApi = {
list: (params: DealsQueryParams) =>
api
.get<PaginatedResponse<Deal>>('/crm/deals', { params })
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<Deal>>(`/crm/deals/${id}`)
.then((r) => r.data),
create: (data: CreateDealPayload) =>
api
.post<SingleResponse<Deal>>('/crm/deals', data)
.then((r) => r.data),
update: (id: string, data: UpdateDealPayload) =>
api
.patch<SingleResponse<Deal>>(`/crm/deals/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Deal>>(`/crm/deals/${id}`)
.then((r) => r.data),
};
// --- Forecast ---
export const forecastApi = {
get: (pipelineId?: string, period?: ForecastPeriod) =>
api
.get<{ success: boolean; data: ForecastResponse; meta: { timestamp: string } }>(
'/crm/deals/forecast',
{ params: { pipelineId, period } },
)
.then((r) => r.data),
};
// --- Pipelines ---
export const pipelinesApi = {
list: () =>
api
.get<{ success: boolean; data: Pipeline[]; meta: { timestamp: string } }>(
'/crm/pipelines',
)
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<Pipeline>>(`/crm/pipelines/${id}`)
.then((r) => r.data),
create: (data: CreatePipelinePayload) =>
api
.post<SingleResponse<Pipeline>>('/crm/pipelines', data)
.then((r) => r.data),
update: (id: string, data: UpdatePipelinePayload) =>
api
.patch<SingleResponse<Pipeline>>(`/crm/pipelines/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Pipeline>>(`/crm/pipelines/${id}`)
.then((r) => r.data),
addStage: (pipelineId: string, data: CreateStagePayload) =>
api
.post<SingleResponse<PipelineStage>>(
`/crm/pipelines/${pipelineId}/stages`,
data,
)
.then((r) => r.data),
updateStage: (pipelineId: string, stageId: string, data: UpdateStagePayload) =>
api
.patch<SingleResponse<PipelineStage>>(
`/crm/pipelines/${pipelineId}/stages/${stageId}`,
data,
)
.then((r) => r.data),
removeStage: (pipelineId: string, stageId: string) =>
api
.delete<SingleResponse<PipelineStage>>(
`/crm/pipelines/${pipelineId}/stages/${stageId}`,
)
.then((r) => r.data),
};
// --- Activities ---
export const activitiesApi = {
list: (params: ActivitiesQueryParams) =>
api
.get<PaginatedResponse<Activity>>('/crm/activities', { params })
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<Activity>>(`/crm/activities/${id}`)
.then((r) => r.data),
create: (data: CreateActivityPayload) =>
api
.post<SingleResponse<Activity>>('/crm/activities', data)
.then((r) => r.data),
update: (id: string, data: UpdateActivityPayload) =>
api
.patch<SingleResponse<Activity>>(`/crm/activities/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Activity>>(`/crm/activities/${id}`)
.then((r) => r.data),
getOpenTasks: () =>
api
.get<{ success: boolean; data: CrmOpenTask[]; meta: { count: number } }>(
'/crm/activities/open-tasks',
)
.then((r) => r.data),
};
// --- Companies ---
export const companiesApi = {
list: (params: CompaniesQueryParams) =>
api
.get<PaginatedResponse<Company>>('/crm/companies', { params })
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<Company>>(`/crm/companies/${id}`)
.then((r) => r.data),
create: (data: CreateCompanyPayload) =>
api
.post<SingleResponse<Company>>('/crm/companies', data)
.then((r) => r.data),
update: (id: string, data: UpdateCompanyPayload) =>
api
.patch<SingleResponse<Company>>(`/crm/companies/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Company>>(`/crm/companies/${id}`)
.then((r) => r.data),
};
// --- Owners (Contact, Company, Deal) ---
export const ownersApi = {
addContactOwner: (contactId: string, data: AddOwnerPayload) =>
api
.post<SingleResponse<EntityOwner>>(`/crm/contacts/${contactId}/owners`, data)
.then((r) => r.data),
removeContactOwner: (contactId: string, userId: string) =>
api
.delete<SingleResponse<EntityOwner>>(`/crm/contacts/${contactId}/owners/${userId}`)
.then((r) => r.data),
addCompanyOwner: (companyId: string, data: AddOwnerPayload) =>
api
.post<SingleResponse<EntityOwner>>(`/crm/companies/${companyId}/owners`, data)
.then((r) => r.data),
removeCompanyOwner: (companyId: string, userId: string) =>
api
.delete<SingleResponse<EntityOwner>>(`/crm/companies/${companyId}/owners/${userId}`)
.then((r) => r.data),
addDealOwner: (dealId: string, data: AddOwnerPayload) =>
api
.post<SingleResponse<EntityOwner>>(`/crm/deals/${dealId}/owners`, data)
.then((r) => r.data),
removeDealOwner: (dealId: string, userId: string) =>
api
.delete<SingleResponse<EntityOwner>>(`/crm/deals/${dealId}/owners/${userId}`)
.then((r) => r.data),
};
// --- Industries ---
export const industriesApi = {
list: () =>
api
.get<{ success: boolean; data: Industry[]; meta: { timestamp: string } }>(
'/crm/industries',
)
.then((r) => r.data),
create: (data: CreateIndustryPayload) =>
api
.post<SingleResponse<Industry>>('/crm/industries', data)
.then((r) => r.data),
update: (id: string, data: UpdateIndustryPayload) =>
api
.patch<SingleResponse<Industry>>(`/crm/industries/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<Industry>>(`/crm/industries/${id}`)
.then((r) => r.data),
};
// --- Account Types ---
export const accountTypesApi = {
list: () =>
api
.get<{ success: boolean; data: AccountType[]; meta: { timestamp: string } }>(
'/crm/account-types',
)
.then((r) => r.data),
create: (data: CreateAccountTypePayload) =>
api
.post<SingleResponse<AccountType>>('/crm/account-types', data)
.then((r) => r.data),
update: (id: string, data: UpdateAccountTypePayload) =>
api
.patch<SingleResponse<AccountType>>(`/crm/account-types/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<AccountType>>(`/crm/account-types/${id}`)
.then((r) => r.data),
};
// --- Relationship Types ---
export const relationshipTypesApi = {
list: () =>
api
.get<{ success: boolean; data: RelationshipType[]; meta: { timestamp: string } }>(
'/crm/relationship-types',
)
.then((r) => r.data),
create: (data: CreateRelationshipTypePayload) =>
api
.post<SingleResponse<RelationshipType>>('/crm/relationship-types', data)
.then((r) => r.data),
update: (id: string, data: UpdateRelationshipTypePayload) =>
api
.patch<SingleResponse<RelationshipType>>(
`/crm/relationship-types/${id}`,
data,
)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<RelationshipType>>(
`/crm/relationship-types/${id}`,
)
.then((r) => r.data),
};
// --- Company Relationships ---
export const companyRelationshipsApi = {
list: (companyId: string) =>
api
.get<{ success: boolean; data: CompanyRelationship[]; meta: { timestamp: string } }>(
`/crm/companies/${companyId}/relationships`,
)
.then((r) => r.data),
create: (companyId: string, data: CreateCompanyRelationshipPayload) =>
api
.post<SingleResponse<CompanyRelationship>>(
`/crm/companies/${companyId}/relationships`,
data,
)
.then((r) => r.data),
delete: (companyId: string, relationshipId: string) =>
api
.delete<SingleResponse<CompanyRelationship>>(
`/crm/companies/${companyId}/relationships/${relationshipId}`,
)
.then((r) => r.data),
};
// --- Tenant Users ---
export const usersApi = {
list: () =>
api
.get<{ success: boolean; data: TenantUser[]; meta: { timestamp: string } }>(
'/crm/users',
)
.then((r) => r.data),
};
// --- Lexware Office: Contacts ---
export const lexwareContactsApi = {
search: (params: LexwareContactSearchParams) =>
api
.get<PaginatedResponse<LexwareContact>>('/crm/lexware/contacts/search', {
params,
})
.then((r) => r.data),
linkCompany: (data: { lexwareContactId: string; companyId: string }) =>
api
.post<SingleResponse<Company>>('/crm/lexware/contacts/link-company', data)
.then((r) => r.data),
linkContact: (data: { lexwareContactId: string; contactId: string }) =>
api
.post<SingleResponse<Contact>>('/crm/lexware/contacts/link-contact', data)
.then((r) => r.data),
unlinkCompany: (companyId: string) =>
api
.delete<SingleResponse<Company>>(
`/crm/lexware/contacts/unlink-company/${companyId}`,
)
.then((r) => r.data),
unlinkContact: (contactId: string) =>
api
.delete<SingleResponse<Contact>>(
`/crm/lexware/contacts/unlink-contact/${contactId}`,
)
.then((r) => r.data),
importCompany: (data: { lexwareContactId: string }) =>
api
.post<SingleResponse<Company>>(
'/crm/lexware/contacts/import-company',
data,
)
.then((r) => r.data),
importContact: (data: { lexwareContactId: string }) =>
api
.post<SingleResponse<Contact>>(
'/crm/lexware/contacts/import-contact',
data,
)
.then((r) => r.data),
push: (entityType: 'company' | 'contact', entityId: string) =>
api
.post<SingleResponse<Company | Contact>>(
`/crm/lexware/contacts/push/${entityType}/${entityId}`,
)
.then((r) => r.data),
sync: (entityType: 'company' | 'contact', entityId: string) =>
api
.post<SingleResponse<Company | Contact>>(
`/crm/lexware/contacts/sync/${entityType}/${entityId}`,
)
.then((r) => r.data),
};
// --- Lexware Office: Vouchers ---
export const lexwareVouchersApi = {
getForCompany: (companyId: string, params?: LexwareVouchersQueryParams) =>
api
.get<PaginatedResponse<LexwareVoucher>>(
`/crm/lexware/vouchers/company/${companyId}`,
{ params },
)
.then((r) => r.data),
getForContact: (contactId: string, params?: LexwareVouchersQueryParams) =>
api
.get<PaginatedResponse<LexwareVoucher>>(
`/crm/lexware/vouchers/contact/${contactId}`,
{ params },
)
.then((r) => r.data),
getForDeal: (dealId: string) =>
api
.get<{ success: boolean; data: DealVoucher[]; meta: { timestamp: string } }>(
`/crm/lexware/vouchers/deal/${dealId}`,
)
.then((r) => r.data),
linkToDeal: (dealId: string, voucherId: string) =>
api
.post<SingleResponse<DealVoucher>>(
`/crm/lexware/vouchers/deal/${dealId}/link`,
{ voucherId },
)
.then((r) => r.data),
unlinkFromDeal: (dealId: string, voucherId: string) =>
api
.delete<SingleResponse<DealVoucher>>(
`/crm/lexware/vouchers/deal/${dealId}/unlink/${voucherId}`,
)
.then((r) => r.data),
refreshCompany: (companyId: string) =>
api
.post<SingleResponse<{ count: number }>>(
`/crm/lexware/vouchers/refresh/company/${companyId}`,
)
.then((r) => r.data),
refreshContact: (contactId: string) =>
api
.post<SingleResponse<{ count: number }>>(
`/crm/lexware/vouchers/refresh/contact/${contactId}`,
)
.then((r) => r.data),
};
// --- Trade Events (Messe-Timer) ---
export const tradeEventsApi = {
list: () =>
api
.get<{ data: TradeEvent[] }>('/crm/trade-events')
.then((r) => r.data),
listActive: () =>
api
.get<{ data: TradeEvent[] }>('/crm/trade-events/active')
.then((r) => r.data),
getById: (id: string) =>
api
.get<SingleResponse<TradeEvent>>(`/crm/trade-events/${id}`)
.then((r) => r.data),
create: (data: CreateTradeEventPayload) =>
api
.post<SingleResponse<TradeEvent>>('/crm/trade-events', data)
.then((r) => r.data),
update: (id: string, data: UpdateTradeEventPayload) =>
api
.patch<SingleResponse<TradeEvent>>(`/crm/trade-events/${id}`, data)
.then((r) => r.data),
delete: (id: string) =>
api
.delete<SingleResponse<TradeEvent>>(`/crm/trade-events/${id}`)
.then((r) => r.data),
};
// --- Custom Fields (Phase 2.1) ---
export const customFieldsApi = {
/** Feld-Definitionen auflisten (optional nach Entity-Typ gefiltert) */
listDefs: (entityType?: CustomFieldEntityType) =>
api
.get<{ success: boolean; data: CustomFieldDef[]; meta: { timestamp: string } }>(
'/crm/custom-fields',
{ params: entityType ? { entityType } : {} },
)
.then((r) => r.data),
/** Einzelne Feld-Definition abrufen */
getDef: (id: string) =>
api
.get<SingleResponse<CustomFieldDef>>(`/crm/custom-fields/${id}`)
.then((r) => r.data),
/** Feld-Definition erstellen */
createDef: (data: CreateCustomFieldDefPayload) =>
api
.post<SingleResponse<CustomFieldDef>>('/crm/custom-fields', data)
.then((r) => r.data),
/** Feld-Definition aktualisieren */
updateDef: (id: string, data: UpdateCustomFieldDefPayload) =>
api
.patch<SingleResponse<CustomFieldDef>>(`/crm/custom-fields/${id}`, data)
.then((r) => r.data),
/** Feld-Definition loeschen (CASCADE auf alle Werte!) */
deleteDef: (id: string) =>
api
.delete<SingleResponse<CustomFieldDef>>(`/crm/custom-fields/${id}`)
.then((r) => r.data),
/** Custom-Field-Werte fuer eine Entity lesen */
getValues: (entityId: string) =>
api
.get<{ success: boolean; data: CustomFieldValue[]; meta: { timestamp: string } }>(
`/crm/custom-fields/${entityId}/values`,
)
.then((r) => r.data),
/** Custom-Field-Werte fuer eine Entity setzen (Bulk-Upsert) */
setValues: (entityId: string, data: SetCustomFieldValuesPayload) =>
api
.put<{ success: boolean; data: CustomFieldValue[]; meta: { timestamp: string } }>(
`/crm/custom-fields/${entityId}/values`,
data,
)
.then((r) => r.data),
};
// --- Import ---
/** Backend erwartet lowercase entity-type-Bezeichner */
const toImportEntityType = (t: ImportEntityType): string =>
t === 'PERSON' ? 'contact' : t === 'COMPANY' ? 'company' : 'deal';
export const importApi = {
preview: (file: File, entityType: ImportEntityType) => {
const form = new FormData();
form.append('file', file);
form.append('entityType', toImportEntityType(entityType));
return api
.post<{ success: boolean; data: ImportPreviewResponse; meta: { timestamp: string } }>(
'/crm/import/preview',
form,
{ headers: { 'Content-Type': 'multipart/form-data' } },
)
.then((r) => r.data);
},
execute: (data: ImportExecuteRequest) => {
// Backend erwartet mapping als Array [{sourceColumn, targetField}]
const mappingArray = Object.entries(data.mapping)
.filter(([, target]) => target)
.map(([sourceColumn, targetField]) => ({ sourceColumn, targetField }));
return api
.post<{ success: boolean; data: ImportExecuteResponse; meta: { timestamp: string } }>(
'/crm/import/execute',
{
...data,
entityType: toImportEntityType(data.entityType),
mapping: mappingArray,
},
)
.then((r) => r.data);
},
};
// --- Enrichment ---
export const enrichmentApi = {
enrich: (companyId: string) =>
api
.post<{ success: boolean; data: EnrichmentResponse; meta: { timestamp: string } }>(
`/crm/companies/${companyId}/enrich`,
)
.then((r) => r.data),
getConfig: () =>
api
.get<{ success: boolean; data: EnrichmentConfig; meta: { timestamp: string } }>(
'/crm/settings/integrations/north-data',
)
.then((r) => r.data),
setConfig: (apiKey: string) =>
api
.put<{ success: boolean; data: EnrichmentConfig; meta: { timestamp: string } }>(
'/crm/settings/integrations/north-data',
{ apiKey },
)
.then((r) => r.data),
};
// --- Contracts ---
export const contractsApi = {
list: (companyId: string, params?: ContractsQueryParams) =>
api
.get<PaginatedResponse<Contract>>(`/crm/companies/${companyId}/contracts`, { params })
.then((r) => r.data),
create: (companyId: string, data: CreateContractPayload) =>
api
.post<SingleResponse<Contract>>(`/crm/companies/${companyId}/contracts`, data)
.then((r) => r.data),
update: (companyId: string, contractId: string, data: UpdateContractPayload) =>
api
.patch<SingleResponse<Contract>>(
`/crm/companies/${companyId}/contracts/${contractId}`,
data,
)
.then((r) => r.data),
delete: (companyId: string, contractId: string) =>
api
.delete<SingleResponse<Contract>>(
`/crm/companies/${companyId}/contracts/${contractId}`,
)
.then((r) => r.data),
};
// --- Contract Files ---
export const contractFilesApi = {
list: (companyId: string, contractId: string) =>
api
.get<{ success: boolean; data: ContractFile[]; meta: { timestamp: string } }>(
`/crm/companies/${companyId}/contracts/${contractId}/files`,
)
.then((r) => r.data),
upload: (companyId: string, contractId: string, file: File) => {
const form = new FormData();
form.append('file', file);
return api
.post<SingleResponse<ContractFile>>(
`/crm/companies/${companyId}/contracts/${contractId}/files`,
form,
{ headers: { 'Content-Type': 'multipart/form-data' } },
)
.then((r) => r.data);
},
download: (companyId: string, contractId: string, fileId: string, inline = false) =>
api
.get(
`/crm/companies/${companyId}/contracts/${contractId}/files/${fileId}/download`,
{
responseType: 'blob',
params: inline ? { inline: 'true' } : undefined,
},
)
.then((r) => r.data as Blob),
delete: (companyId: string, contractId: string, fileId: string) =>
api
.delete<SingleResponse<ContractFile>>(
`/crm/companies/${companyId}/contracts/${contractId}/files/${fileId}`,
)
.then((r) => r.data),
};
// --- Microsoft 365 Integrations ---
export const integrationsApi = {
list: () =>
api
.get<{ success: boolean; data: UserIntegration[]; meta: { timestamp: string } }>(
'/users/me/integrations',
)
.then((r) => r.data),
disconnectM365: () =>
api
.delete<{ success: boolean; meta: { timestamp: string } }>(
'/users/me/integrations/microsoft-365',
)
.then((r) => r.data),
/** Ruft die Microsoft OAuth-URL via API ab (JWT-geschützt) und leitet den Browser weiter */
connectM365: () =>
api
.get<{ success: boolean; data: { url: string } }>(
'/auth/integrations/microsoft-365',
)
.then((r) => {
window.location.href = r.data.data.url;
}),
};
// --- Microsoft Graph Proxy (CRM) ---
export const graphApi = {
getContactEmails: (contactId: string) =>
api
.get<{ success: boolean; data: M365Email[]; meta: { timestamp: string } }>(
`/crm/contacts/${contactId}/emails`,
)
.then((r) => r.data),
getContactCalendar: (contactId: string) =>
api
.get<{ success: boolean; data: M365CalendarEvent[]; meta: { timestamp: string } }>(
`/crm/contacts/${contactId}/calendar`,
)
.then((r) => r.data),
getContactTasks: (contactId: string) =>
api
.get<{ success: boolean; data: M365TaskList[]; meta: { timestamp: string } }>(
`/crm/contacts/${contactId}/tasks`,
)
.then((r) => r.data),
};
// --- Office365 Übersicht (globale Daten) ---
export const office365Api = {
getEmails: () =>
api
.get<{ success: boolean; data: M365Email[]; meta: { count: number } }>(
'/crm/office365/emails',
)
.then((r) => r.data),
getCalendar: () =>
api
.get<{ success: boolean; data: M365CalendarEvent[]; meta: { count: number } }>(
'/crm/office365/calendar',
)
.then((r) => r.data),
getContacts: () =>
api
.get<{ success: boolean; data: M365Contact[]; meta: { count: number } }>(
'/crm/office365/contacts',
)
.then((r) => r.data),
getTasks: () =>
api
.get<{ success: boolean; data: M365TaskList[]; meta: { listCount: number; taskCount: number } }>(
'/crm/office365/tasks',
)
.then((r) => r.data),
getCalendarRange: (startDate: string, endDate: string) =>
api
.get<{ success: boolean; data: M365CalendarEvent[]; meta: { count: number } }>(
'/crm/office365/calendar/range',
{ params: { startDate, endDate } },
)
.then((r) => r.data),
getMailFolders: () =>
api
.get<{ success: boolean; data: M365MailFolder[]; meta: { count: number } }>(
'/crm/office365/folders',
)
.then((r) => r.data),
getMailsInFolder: (folderId: string, days: number) =>
api
.get<{ success: boolean; data: M365Email[]; meta: { count: number } }>(
`/crm/office365/folders/${folderId}/messages`,
{ params: { days } },
)
.then((r) => r.data),
getTasksFlat: () =>
api
.get<{ success: boolean; data: M365TaskFlat[]; meta: { count: number } }>(
'/crm/office365/tasks/flat',
)
.then((r) => r.data),
createTask: (title: string, bodyContent?: string, dueDateISO?: string) =>
api
.post<{ success: boolean; data: { id: string; listId: string } }>(
'/crm/office365/tasks',
{ title, bodyContent, dueDateISO },
)
.then((r) => r.data),
completeTask: (listId: string, taskId: string) =>
api
.patch<{ success: boolean }>(
`/crm/office365/tasks/${listId}/${taskId}/complete`,
{},
)
.then((r) => r.data),
getM365Profile: () =>
api
.get<{ success: boolean; data: M365UserProfile }>('/crm/office365/profile')
.then((r) => r.data),
getM365Photo: () =>
api
.get<{ success: boolean; data: { photoBase64: string | null } }>('/crm/office365/photo')
.then((r) => r.data),
};