task-d complate

This commit is contained in:
phaichayon
2026-06-15 13:46:33 +07:00
parent 8148850fda
commit 1d2406483e
32 changed files with 4812 additions and 39 deletions

View File

@@ -0,0 +1,74 @@
import { mutationOptions } from '@tanstack/react-query';
import { getQueryClient } from '@/lib/query-client';
import {
createEnquiry,
createEnquiryFollowup,
deleteEnquiry,
deleteEnquiryFollowup,
updateEnquiry,
updateEnquiryFollowup
} from './service';
import { enquiryKeys } from './queries';
import type { EnquiryFollowupMutationPayload, EnquiryMutationPayload } from './types';
export const createEnquiryMutation = mutationOptions({
mutationFn: (data: EnquiryMutationPayload) => createEnquiry(data),
onSuccess: () => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.all });
}
});
export const updateEnquiryMutation = mutationOptions({
mutationFn: ({ id, values }: { id: string; values: EnquiryMutationPayload }) =>
updateEnquiry(id, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.all });
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.detail(variables.id) });
}
});
export const deleteEnquiryMutation = mutationOptions({
mutationFn: (id: string) => deleteEnquiry(id),
onSuccess: () => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.all });
}
});
export const createEnquiryFollowupMutation = mutationOptions({
mutationFn: ({
enquiryId,
values
}: {
enquiryId: string;
values: EnquiryFollowupMutationPayload;
}) => createEnquiryFollowup(enquiryId, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.followups(variables.enquiryId) });
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.detail(variables.enquiryId) });
}
});
export const updateEnquiryFollowupMutation = mutationOptions({
mutationFn: ({
enquiryId,
followupId,
values
}: {
enquiryId: string;
followupId: string;
values: EnquiryFollowupMutationPayload;
}) => updateEnquiryFollowup(enquiryId, followupId, values),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.followups(variables.enquiryId) });
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.detail(variables.enquiryId) });
}
});
export const deleteEnquiryFollowupMutation = mutationOptions({
mutationFn: ({ enquiryId, followupId }: { enquiryId: string; followupId: string }) =>
deleteEnquiryFollowup(enquiryId, followupId),
onSuccess: (_data, variables) => {
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.followups(variables.enquiryId) });
getQueryClient().invalidateQueries({ queryKey: enquiryKeys.detail(variables.enquiryId) });
}
});

View File

@@ -0,0 +1,28 @@
import { queryOptions } from '@tanstack/react-query';
import { getEnquiries, getEnquiryById, getEnquiryFollowups } from './service';
import type { EnquiryFilters } from './types';
export const enquiryKeys = {
all: ['crm-enquiries'] as const,
list: (filters: EnquiryFilters) => [...enquiryKeys.all, 'list', filters] as const,
detail: (id: string) => [...enquiryKeys.all, 'detail', id] as const,
followups: (id: string) => [...enquiryKeys.all, 'followups', id] as const
};
export const enquiriesQueryOptions = (filters: EnquiryFilters) =>
queryOptions({
queryKey: enquiryKeys.list(filters),
queryFn: () => getEnquiries(filters)
});
export const enquiryByIdOptions = (id: string) =>
queryOptions({
queryKey: enquiryKeys.detail(id),
queryFn: () => getEnquiryById(id)
});
export const enquiryFollowupsOptions = (id: string) =>
queryOptions({
queryKey: enquiryKeys.followups(id),
queryFn: () => getEnquiryFollowups(id)
});

View File

@@ -0,0 +1,83 @@
import { apiClient } from '@/lib/api-client';
import type {
EnquiryDetailResponse,
EnquiryFilters,
EnquiryFollowupMutationPayload,
EnquiryFollowupsResponse,
EnquiryListResponse,
EnquiryMutationPayload,
MutationSuccessResponse
} from './types';
export async function getEnquiries(filters: EnquiryFilters): Promise<EnquiryListResponse> {
const searchParams = new URLSearchParams();
if (filters.page) searchParams.set('page', String(filters.page));
if (filters.limit) searchParams.set('limit', String(filters.limit));
if (filters.search) searchParams.set('search', filters.search);
if (filters.status) searchParams.set('status', filters.status);
if (filters.productType) searchParams.set('productType', filters.productType);
if (filters.priority) searchParams.set('priority', filters.priority);
if (filters.branch) searchParams.set('branch', filters.branch);
if (filters.customer) searchParams.set('customer', filters.customer);
if (filters.sort) searchParams.set('sort', filters.sort);
const query = searchParams.toString();
return apiClient<EnquiryListResponse>(`/crm/enquiries${query ? `?${query}` : ''}`);
}
export async function getEnquiryById(id: string): Promise<EnquiryDetailResponse> {
return apiClient<EnquiryDetailResponse>(`/crm/enquiries/${id}`);
}
export async function createEnquiry(data: EnquiryMutationPayload) {
return apiClient<MutationSuccessResponse>('/crm/enquiries', {
method: 'POST',
body: JSON.stringify(data)
});
}
export async function updateEnquiry(id: string, data: EnquiryMutationPayload) {
return apiClient<MutationSuccessResponse>(`/crm/enquiries/${id}`, {
method: 'PATCH',
body: JSON.stringify(data)
});
}
export async function deleteEnquiry(id: string) {
return apiClient<MutationSuccessResponse>(`/crm/enquiries/${id}`, {
method: 'DELETE'
});
}
export async function getEnquiryFollowups(id: string): Promise<EnquiryFollowupsResponse> {
return apiClient<EnquiryFollowupsResponse>(`/crm/enquiries/${id}/followups`);
}
export async function createEnquiryFollowup(
enquiryId: string,
data: EnquiryFollowupMutationPayload
) {
return apiClient<MutationSuccessResponse>(`/crm/enquiries/${enquiryId}/followups`, {
method: 'POST',
body: JSON.stringify(data)
});
}
export async function updateEnquiryFollowup(
enquiryId: string,
followupId: string,
data: EnquiryFollowupMutationPayload
) {
return apiClient<MutationSuccessResponse>(`/crm/enquiries/${enquiryId}/followups/${followupId}`, {
method: 'PATCH',
body: JSON.stringify(data)
});
}
export async function deleteEnquiryFollowup(enquiryId: string, followupId: string) {
return apiClient<MutationSuccessResponse>(`/crm/enquiries/${enquiryId}/followups/${followupId}`, {
method: 'DELETE'
});
}

View File

@@ -0,0 +1,189 @@
export interface EnquiryOption {
id: string;
code: string;
label: string;
value: string | null;
}
export interface EnquiryBranchOption {
id: string;
code: string;
name: string;
value: string | null;
}
export interface EnquiryCustomerLookup {
id: string;
code: string;
name: string;
branchId: string | null;
}
export interface EnquiryContactLookup {
id: string;
customerId: string;
name: string;
email: string | null;
mobile: string | null;
isPrimary: boolean;
}
export interface EnquiryRecord {
id: string;
organizationId: string;
branchId: string | null;
code: string;
customerId: string;
contactId: string | null;
title: string;
description: string | null;
requirement: string | null;
projectName: string | null;
projectLocation: string | null;
productType: string;
status: string;
priority: string;
leadChannel: string | null;
estimatedValue: number | null;
chancePercent: number | null;
expectedCloseDate: string | null;
competitor: string | null;
source: string | null;
notes: string | null;
isHotProject: boolean;
isActive: boolean;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
createdBy: string;
updatedBy: string;
}
export interface EnquiryListItem extends EnquiryRecord {
customerName: string;
contactName: string | null;
followupCount: number;
}
export interface EnquiryFollowupRecord {
id: string;
organizationId: string;
enquiryId: string;
followupDate: string;
followupType: string;
contactId: string | null;
outcome: string | null;
notes: string | null;
nextFollowupDate: string | null;
nextAction: string | null;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
createdBy: string;
updatedBy: string;
}
export interface EnquiryActivityRecord {
id: string;
action: string;
entityType: string;
entityId: string;
createdAt: string;
userId: string;
actorName: string | null;
}
export interface EnquiryReferenceData {
branches: EnquiryBranchOption[];
statuses: EnquiryOption[];
productTypes: EnquiryOption[];
priorities: EnquiryOption[];
leadChannels: EnquiryOption[];
followupTypes: EnquiryOption[];
customers: EnquiryCustomerLookup[];
contacts: EnquiryContactLookup[];
}
export interface EnquiryFilters {
page?: number;
limit?: number;
search?: string;
status?: string;
productType?: string;
priority?: string;
branch?: string;
customer?: string;
sort?: string;
}
export interface EnquiryListResponse {
success: boolean;
time: string;
message: string;
totalItems: number;
offset: number;
limit: number;
items: EnquiryListItem[];
}
export interface EnquiryDetailResponse {
success: boolean;
time: string;
message: string;
enquiry: EnquiryRecord;
activity: EnquiryActivityRecord[];
}
export interface EnquiryFollowupsResponse {
success: boolean;
time: string;
message: string;
items: EnquiryFollowupRecord[];
}
export interface EnquiryMutationPayload {
customerId: string;
contactId?: string | null;
title: string;
description?: string;
requirement?: string;
projectName?: string;
projectLocation?: string;
branchId?: string | null;
productType: string;
status: string;
priority: string;
leadChannel?: string | null;
estimatedValue?: number | null;
chancePercent?: number | null;
expectedCloseDate?: string | null;
competitor?: string;
source?: string;
isHotProject?: boolean;
notes?: string;
isActive?: boolean;
}
export interface EnquiryFollowupMutationPayload {
followupDate: string;
followupType: string;
contactId?: string | null;
outcome?: string;
notes?: string;
nextFollowupDate?: string | null;
nextAction?: string;
}
export interface EnquiryCustomerRelationItem {
id: string;
code: string;
title: string;
status: string;
productType: string;
updatedAt: string;
}
export interface MutationSuccessResponse {
success: boolean;
message: string;
}