Deploy rule, CRM enhancements, Company Employee category, bilingual, contacts module completion
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
65
frontend/src/lib/api/categories.ts
Normal file
65
frontend/src/lib/api/categories.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { api } from '../api'
|
||||
|
||||
export interface Category {
|
||||
id: string
|
||||
name: string
|
||||
nameAr?: string
|
||||
parentId?: string
|
||||
parent?: Category
|
||||
children?: Category[]
|
||||
description?: string
|
||||
isActive: boolean
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
_count?: {
|
||||
contacts: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface CreateCategoryData {
|
||||
name: string
|
||||
nameAr?: string
|
||||
parentId?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface UpdateCategoryData extends Partial<CreateCategoryData> {
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export const categoriesAPI = {
|
||||
// Get all categories (flat list)
|
||||
getAll: async (): Promise<Category[]> => {
|
||||
const response = await api.get('/contacts/categories')
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Get category tree (hierarchical)
|
||||
getTree: async (): Promise<Category[]> => {
|
||||
const response = await api.get('/contacts/categories/tree')
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Get single category by ID
|
||||
getById: async (id: string): Promise<Category> => {
|
||||
const response = await api.get(`/contacts/categories/${id}`)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Create new category
|
||||
create: async (data: CreateCategoryData): Promise<Category> => {
|
||||
const response = await api.post('/contacts/categories', data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Update existing category
|
||||
update: async (id: string, data: UpdateCategoryData): Promise<Category> => {
|
||||
const response = await api.put(`/contacts/categories/${id}`, data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Delete category
|
||||
delete: async (id: string): Promise<void> => {
|
||||
await api.delete(`/contacts/categories/${id}`)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,9 @@ export interface Contact {
|
||||
customFields?: any
|
||||
categories?: any[]
|
||||
parent?: any
|
||||
parentId?: string
|
||||
employeeId?: string | null
|
||||
employee?: { id: string; firstName: string; lastName: string; email: string; uniqueEmployeeId?: string }
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
createdBy?: any
|
||||
@@ -49,6 +52,7 @@ export interface CreateContactData {
|
||||
categories?: string[]
|
||||
tags?: string[]
|
||||
parentId?: string
|
||||
employeeId?: string | null
|
||||
source: string
|
||||
customFields?: any
|
||||
}
|
||||
@@ -143,11 +147,13 @@ export const contactsAPI = {
|
||||
},
|
||||
|
||||
// Export contacts
|
||||
export: async (filters: ContactFilters = {}): Promise<Blob> => {
|
||||
export: async (filters: ContactFilters & { excludeCompanyEmployees?: boolean } = {}): Promise<Blob> => {
|
||||
const params = new URLSearchParams()
|
||||
if (filters.search) params.append('search', filters.search)
|
||||
if (filters.type) params.append('type', filters.type)
|
||||
if (filters.status) params.append('status', filters.status)
|
||||
if (filters.category) params.append('category', filters.category)
|
||||
if (filters.excludeCompanyEmployees) params.append('excludeCompanyEmployees', 'true')
|
||||
|
||||
const response = await api.get(`/contacts/export?${params.toString()}`, {
|
||||
responseType: 'blob'
|
||||
@@ -156,7 +162,12 @@ export const contactsAPI = {
|
||||
},
|
||||
|
||||
// Import contacts
|
||||
import: async (file: File): Promise<{ success: number; errors: any[] }> => {
|
||||
import: async (file: File): Promise<{
|
||||
success: number
|
||||
failed: number
|
||||
duplicates: number
|
||||
errors: Array<{ row: number; field: string; message: string; data?: any }>
|
||||
}> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
@@ -166,6 +177,51 @@ export const contactsAPI = {
|
||||
}
|
||||
})
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Check for duplicates
|
||||
checkDuplicates: async (data: {
|
||||
email?: string
|
||||
phone?: string
|
||||
mobile?: string
|
||||
taxNumber?: string
|
||||
commercialRegister?: string
|
||||
excludeId?: string
|
||||
}): Promise<Contact[]> => {
|
||||
const response = await api.post('/contacts/check-duplicates', data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
// Relationship management
|
||||
getRelationships: async (contactId: string): Promise<any[]> => {
|
||||
const response = await api.get(`/contacts/${contactId}/relationships`)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
addRelationship: async (contactId: string, data: {
|
||||
toContactId: string
|
||||
type: string
|
||||
startDate: string
|
||||
endDate?: string
|
||||
notes?: string
|
||||
}): Promise<any> => {
|
||||
const response = await api.post(`/contacts/${contactId}/relationships`, data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
updateRelationship: async (contactId: string, relationshipId: string, data: {
|
||||
type?: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
notes?: string
|
||||
isActive?: boolean
|
||||
}): Promise<any> => {
|
||||
const response = await api.put(`/contacts/${contactId}/relationships/${relationshipId}`, data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
deleteRelationship: async (contactId: string, relationshipId: string): Promise<void> => {
|
||||
await api.delete(`/contacts/${contactId}/relationships/${relationshipId}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
frontend/src/lib/api/pipelines.ts
Normal file
30
frontend/src/lib/api/pipelines.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { api } from '../api'
|
||||
|
||||
export interface PipelineStage {
|
||||
name: string
|
||||
nameAr?: string
|
||||
order: number
|
||||
}
|
||||
|
||||
export interface Pipeline {
|
||||
id: string
|
||||
name: string
|
||||
nameAr?: string
|
||||
structure: string
|
||||
stages: PipelineStage[]
|
||||
isActive: boolean
|
||||
}
|
||||
|
||||
export const pipelinesAPI = {
|
||||
getAll: async (structure?: string): Promise<Pipeline[]> => {
|
||||
const params = new URLSearchParams()
|
||||
if (structure) params.append('structure', structure)
|
||||
const response = await api.get(`/crm/pipelines?${params.toString()}`)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
getById: async (id: string): Promise<Pipeline> => {
|
||||
const response = await api.get(`/crm/pipelines/${id}`)
|
||||
return response.data.data
|
||||
}
|
||||
}
|
||||
74
frontend/src/lib/api/quotes.ts
Normal file
74
frontend/src/lib/api/quotes.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import { api } from '../api'
|
||||
|
||||
export interface QuoteItem {
|
||||
description: string
|
||||
quantity: number
|
||||
unitPrice: number
|
||||
total: number
|
||||
}
|
||||
|
||||
export interface Quote {
|
||||
id: string
|
||||
quoteNumber: string
|
||||
dealId: string
|
||||
deal?: any
|
||||
version: number
|
||||
items: QuoteItem[] | any
|
||||
subtotal: number
|
||||
discountType?: string
|
||||
discountValue?: number
|
||||
taxRate: number
|
||||
taxAmount: number
|
||||
total: number
|
||||
validUntil: string
|
||||
paymentTerms?: string
|
||||
deliveryTerms?: string
|
||||
notes?: string
|
||||
status: string
|
||||
sentAt?: string
|
||||
viewedAt?: string
|
||||
approvedBy?: string
|
||||
approvedAt?: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface CreateQuoteData {
|
||||
dealId: string
|
||||
items: QuoteItem[] | any[]
|
||||
subtotal: number
|
||||
taxRate: number
|
||||
taxAmount: number
|
||||
total: number
|
||||
validUntil: string
|
||||
paymentTerms?: string
|
||||
deliveryTerms?: string
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export const quotesAPI = {
|
||||
getByDeal: async (dealId: string): Promise<Quote[]> => {
|
||||
const response = await api.get(`/crm/deals/${dealId}/quotes`)
|
||||
return response.data.data || []
|
||||
},
|
||||
|
||||
getById: async (id: string): Promise<Quote> => {
|
||||
const response = await api.get(`/crm/quotes/${id}`)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
create: async (data: CreateQuoteData): Promise<Quote> => {
|
||||
const response = await api.post('/crm/quotes', data)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
approve: async (id: string): Promise<Quote> => {
|
||||
const response = await api.post(`/crm/quotes/${id}/approve`)
|
||||
return response.data.data
|
||||
},
|
||||
|
||||
send: async (id: string): Promise<Quote> => {
|
||||
const response = await api.post(`/crm/quotes/${id}/send`)
|
||||
return response.data.data
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user