[SAAS-021] feat: Implement MLM module frontend
- 4 API services: structures, ranks, nodes, commissions - 30+ React Query hooks in useMlm.ts - My network/dashboard hooks for user experience - Tree visualization support hooks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
69a6a2c4ea
commit
eff0f70b7f
@ -15,3 +15,4 @@ export * from './useMfa';
|
||||
export * from './useWhatsApp';
|
||||
export * from './usePortfolio';
|
||||
export * from './useGoals';
|
||||
export * from './useMlm';
|
||||
|
||||
302
src/hooks/useMlm.ts
Normal file
302
src/hooks/useMlm.ts
Normal file
@ -0,0 +1,302 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
structuresApi,
|
||||
ranksApi,
|
||||
nodesApi,
|
||||
commissionsApi,
|
||||
type StructureFilters,
|
||||
type CreateStructureDto,
|
||||
type UpdateStructureDto,
|
||||
type RankFilters,
|
||||
type CreateRankDto,
|
||||
type UpdateRankDto,
|
||||
type NodeFilters,
|
||||
type CreateNodeDto,
|
||||
type UpdateNodeDto,
|
||||
type NodeStatus,
|
||||
type CommissionFilters,
|
||||
type CalculateCommissionsDto,
|
||||
type CommissionStatus,
|
||||
} from '@/services/mlm';
|
||||
|
||||
// ============================================
|
||||
// Structures Hooks
|
||||
// ============================================
|
||||
|
||||
export function useStructures(filters?: StructureFilters) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'structures', filters],
|
||||
queryFn: () => structuresApi.list(filters),
|
||||
});
|
||||
}
|
||||
|
||||
export function useStructure(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'structures', id],
|
||||
queryFn: () => structuresApi.get(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateStructure() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateStructureDto) => structuresApi.create(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'structures'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateStructure() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: UpdateStructureDto }) =>
|
||||
structuresApi.update(id, data),
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'structures'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'structures', id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteStructure() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => structuresApi.delete(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'structures'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Ranks Hooks
|
||||
// ============================================
|
||||
|
||||
export function useRanks(filters?: RankFilters) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'ranks', filters],
|
||||
queryFn: () => ranksApi.list(filters),
|
||||
});
|
||||
}
|
||||
|
||||
export function useRank(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'ranks', id],
|
||||
queryFn: () => ranksApi.get(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateRank() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateRankDto) => ranksApi.create(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'ranks'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateRank() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: UpdateRankDto }) =>
|
||||
ranksApi.update(id, data),
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'ranks'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'ranks', id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteRank() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => ranksApi.delete(id),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'ranks'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useEvaluateRanks() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (structureId: string) => ranksApi.evaluate(structureId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Nodes Hooks
|
||||
// ============================================
|
||||
|
||||
export function useNodes(filters?: NodeFilters) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'nodes', filters],
|
||||
queryFn: () => nodesApi.list(filters),
|
||||
});
|
||||
}
|
||||
|
||||
export function useNode(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'nodes', id],
|
||||
queryFn: () => nodesApi.get(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateNode() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: CreateNodeDto) => nodesApi.create(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'my'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateNode() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: UpdateNodeDto }) =>
|
||||
nodesApi.update(id, data),
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes', id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateNodeStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, status }: { id: string; status: NodeStatus }) =>
|
||||
nodesApi.updateStatus(id, { status }),
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes', id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useNodeDownline(nodeId: string, maxDepth?: number) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'nodes', nodeId, 'downline', maxDepth],
|
||||
queryFn: () => nodesApi.getDownline(nodeId, maxDepth),
|
||||
enabled: !!nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useNodeUpline(nodeId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'nodes', nodeId, 'upline'],
|
||||
queryFn: () => nodesApi.getUpline(nodeId),
|
||||
enabled: !!nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
export function useNodeTree(nodeId: string, maxDepth?: number) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'nodes', nodeId, 'tree', maxDepth],
|
||||
queryFn: () => nodesApi.getTree(nodeId, maxDepth),
|
||||
enabled: !!nodeId,
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// My Network Hooks
|
||||
// ============================================
|
||||
|
||||
export function useMyDashboard() {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'my', 'dashboard'],
|
||||
queryFn: () => nodesApi.getMyDashboard(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMyNetwork(maxDepth?: number) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'my', 'network', maxDepth],
|
||||
queryFn: () => nodesApi.getMyNetwork(maxDepth),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMyEarnings() {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'my', 'earnings'],
|
||||
queryFn: () => nodesApi.getMyEarnings(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMyRank() {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'my', 'rank'],
|
||||
queryFn: () => nodesApi.getMyRank(),
|
||||
});
|
||||
}
|
||||
|
||||
export function useGenerateInviteLink() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: () => nodesApi.generateInviteLink(),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'my'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Commissions Hooks
|
||||
// ============================================
|
||||
|
||||
export function useMLMCommissions(filters?: CommissionFilters) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'commissions', filters],
|
||||
queryFn: () => commissionsApi.list(filters),
|
||||
});
|
||||
}
|
||||
|
||||
export function useMLMCommission(id: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'commissions', id],
|
||||
queryFn: () => commissionsApi.get(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
}
|
||||
|
||||
export function useCalculateCommissions() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: CalculateCommissionsDto) => commissionsApi.calculate(data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'commissions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'nodes'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'my'] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useUpdateCommissionStatus() {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, status }: { id: string; status: CommissionStatus }) =>
|
||||
commissionsApi.updateStatus(id, { status }),
|
||||
onSuccess: (_, { id }) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'commissions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mlm', 'commissions', id] });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCommissionsByLevel(nodeId?: string) {
|
||||
return useQuery({
|
||||
queryKey: ['mlm', 'commissions', 'by-level', nodeId],
|
||||
queryFn: () => commissionsApi.getByLevel(nodeId),
|
||||
});
|
||||
}
|
||||
138
src/services/mlm/commissions.api.ts
Normal file
138
src/services/mlm/commissions.api.ts
Normal file
@ -0,0 +1,138 @@
|
||||
import api from '../api';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export type CommissionType = 'level' | 'matching' | 'infinity' | 'leadership' | 'pool';
|
||||
export type CommissionStatus = 'pending' | 'approved' | 'paid' | 'cancelled';
|
||||
export type BonusType = 'rank_achievement' | 'rank_maintenance' | 'fast_start' | 'pool_share';
|
||||
|
||||
export interface Commission {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
nodeId: string;
|
||||
sourceNodeId: string;
|
||||
type: CommissionType;
|
||||
level: number;
|
||||
sourceAmount: number;
|
||||
rateApplied: number;
|
||||
commissionAmount: number;
|
||||
currency: string;
|
||||
periodId: string | null;
|
||||
sourceReference: string | null;
|
||||
status: CommissionStatus;
|
||||
paidAt: string | null;
|
||||
notes: string | null;
|
||||
createdAt: string;
|
||||
node?: {
|
||||
id: string;
|
||||
userId: string;
|
||||
user?: {
|
||||
email: string;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
};
|
||||
};
|
||||
sourceNode?: {
|
||||
id: string;
|
||||
userId: string;
|
||||
user?: {
|
||||
email: string;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface CalculateCommissionsDto {
|
||||
sourceNodeId: string;
|
||||
amount: number;
|
||||
currency?: string;
|
||||
sourceReference?: string;
|
||||
periodId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateCommissionStatusDto {
|
||||
status: CommissionStatus;
|
||||
}
|
||||
|
||||
export interface CommissionFilters {
|
||||
nodeId?: string;
|
||||
sourceNodeId?: string;
|
||||
type?: CommissionType;
|
||||
level?: number;
|
||||
status?: CommissionStatus;
|
||||
periodId?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedCommissions {
|
||||
items: Commission[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface CommissionsByLevel {
|
||||
level: number;
|
||||
count: number;
|
||||
totalAmount: number;
|
||||
}
|
||||
|
||||
export interface EarningsSummary {
|
||||
totalCommissions: number;
|
||||
totalBonuses: number;
|
||||
totalEarnings: number;
|
||||
pendingAmount: number;
|
||||
paidAmount: number;
|
||||
byLevel: CommissionsByLevel[];
|
||||
}
|
||||
|
||||
export interface Bonus {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
nodeId: string;
|
||||
rankId: string | null;
|
||||
type: BonusType;
|
||||
amount: number;
|
||||
currency: string;
|
||||
periodId: string | null;
|
||||
status: CommissionStatus;
|
||||
paidAt: string | null;
|
||||
achievedAt: string;
|
||||
notes: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// API Service
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export const commissionsApi = {
|
||||
list: async (params?: CommissionFilters): Promise<PaginatedCommissions> => {
|
||||
const response = await api.get<PaginatedCommissions>('/mlm/commissions', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
get: async (id: string): Promise<Commission> => {
|
||||
const response = await api.get<Commission>(`/mlm/commissions/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
calculate: async (data: CalculateCommissionsDto): Promise<Commission[]> => {
|
||||
const response = await api.post<Commission[]>('/mlm/commissions/calculate', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateStatus: async (id: string, data: UpdateCommissionStatusDto): Promise<Commission> => {
|
||||
const response = await api.patch<Commission>(`/mlm/commissions/${id}/status`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getByLevel: async (nodeId?: string): Promise<CommissionsByLevel[]> => {
|
||||
const response = await api.get<CommissionsByLevel[]>('/mlm/commissions/by-level', {
|
||||
params: { nodeId },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
4
src/services/mlm/index.ts
Normal file
4
src/services/mlm/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './structures.api';
|
||||
export * from './ranks.api';
|
||||
export * from './nodes.api';
|
||||
export * from './commissions.api';
|
||||
208
src/services/mlm/nodes.api.ts
Normal file
208
src/services/mlm/nodes.api.ts
Normal file
@ -0,0 +1,208 @@
|
||||
import api from '../api';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export type NodeStatus = 'pending' | 'active' | 'inactive' | 'suspended';
|
||||
|
||||
export interface Node {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
structureId: string;
|
||||
userId: string;
|
||||
parentId: string | null;
|
||||
sponsorId: string | null;
|
||||
position: number | null;
|
||||
path: string | null;
|
||||
depth: number;
|
||||
rankId: string | null;
|
||||
highestRankId: string | null;
|
||||
personalVolume: number;
|
||||
groupVolume: number;
|
||||
directReferrals: number;
|
||||
totalDownline: number;
|
||||
totalEarnings: number;
|
||||
status: NodeStatus;
|
||||
joinedAt: string;
|
||||
inviteCode: string | null;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
};
|
||||
rank?: {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
color: string | null;
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateNodeDto {
|
||||
structureId: string;
|
||||
userId: string;
|
||||
parentId?: string;
|
||||
sponsorId?: string;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
export interface UpdateNodeDto {
|
||||
parentId?: string;
|
||||
position?: number;
|
||||
}
|
||||
|
||||
export interface UpdateNodeStatusDto {
|
||||
status: NodeStatus;
|
||||
}
|
||||
|
||||
export interface NodeFilters {
|
||||
structureId?: string;
|
||||
parentId?: string;
|
||||
sponsorId?: string;
|
||||
status?: NodeStatus;
|
||||
minDepth?: number;
|
||||
maxDepth?: number;
|
||||
search?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface PaginatedNodes {
|
||||
items: Node[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface TreeNode {
|
||||
id: string;
|
||||
userId: string;
|
||||
depth: number;
|
||||
position: number | null;
|
||||
personalVolume: number;
|
||||
groupVolume: number;
|
||||
directReferrals: number;
|
||||
status: NodeStatus;
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
firstName: string | null;
|
||||
lastName: string | null;
|
||||
};
|
||||
rank?: {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
color: string | null;
|
||||
badgeUrl: string | null;
|
||||
};
|
||||
children: TreeNode[];
|
||||
}
|
||||
|
||||
export interface InviteLink {
|
||||
inviteCode: string;
|
||||
inviteUrl: string;
|
||||
}
|
||||
|
||||
export interface MyNetworkSummary {
|
||||
totalDownline: number;
|
||||
directReferrals: number;
|
||||
activeDownline: number;
|
||||
personalVolume: number;
|
||||
groupVolume: number;
|
||||
totalEarnings: number;
|
||||
currentRank: {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
} | null;
|
||||
nextRank: {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
requirements: Record<string, unknown>;
|
||||
progress: Record<string, number>;
|
||||
} | null;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// API Service
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export const nodesApi = {
|
||||
// Nodes CRUD
|
||||
list: async (params?: NodeFilters): Promise<PaginatedNodes> => {
|
||||
const response = await api.get<PaginatedNodes>('/mlm/nodes', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
get: async (id: string): Promise<Node> => {
|
||||
const response = await api.get<Node>(`/mlm/nodes/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: CreateNodeDto): Promise<Node> => {
|
||||
const response = await api.post<Node>('/mlm/nodes', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string, data: UpdateNodeDto): Promise<Node> => {
|
||||
const response = await api.patch<Node>(`/mlm/nodes/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
updateStatus: async (id: string, data: UpdateNodeStatusDto): Promise<Node> => {
|
||||
const response = await api.patch<Node>(`/mlm/nodes/${id}/status`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Network navigation
|
||||
getDownline: async (nodeId: string, maxDepth?: number): Promise<Node[]> => {
|
||||
const response = await api.get<Node[]>(`/mlm/nodes/${nodeId}/downline`, {
|
||||
params: { maxDepth },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getUpline: async (nodeId: string): Promise<Node[]> => {
|
||||
const response = await api.get<Node[]>(`/mlm/nodes/${nodeId}/upline`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getTree: async (nodeId: string, maxDepth?: number): Promise<TreeNode> => {
|
||||
const response = await api.get<TreeNode>(`/mlm/nodes/${nodeId}/tree`, {
|
||||
params: { maxDepth },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// My Network
|
||||
getMyDashboard: async (): Promise<MyNetworkSummary> => {
|
||||
const response = await api.get<MyNetworkSummary>('/mlm/my/dashboard');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMyNetwork: async (maxDepth?: number): Promise<TreeNode | null> => {
|
||||
const response = await api.get<TreeNode | null>('/mlm/my/network', {
|
||||
params: { maxDepth },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMyEarnings: async () => {
|
||||
const response = await api.get('/mlm/my/earnings');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
getMyRank: async () => {
|
||||
const response = await api.get('/mlm/my/rank');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
generateInviteLink: async (): Promise<InviteLink> => {
|
||||
const response = await api.post<InviteLink>('/mlm/my/invite');
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
94
src/services/mlm/ranks.api.ts
Normal file
94
src/services/mlm/ranks.api.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import api from '../api';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export interface RankRequirements {
|
||||
personalVolume?: number;
|
||||
groupVolume?: number;
|
||||
directReferrals?: number;
|
||||
activeLegs?: number;
|
||||
rankInLegs?: {
|
||||
rankLevel: number;
|
||||
count: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface RankBenefits {
|
||||
discount?: number;
|
||||
access?: string[];
|
||||
features?: string[];
|
||||
}
|
||||
|
||||
export interface Rank {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
structureId: string;
|
||||
name: string;
|
||||
level: number;
|
||||
badgeUrl: string | null;
|
||||
color: string | null;
|
||||
requirements: RankRequirements;
|
||||
bonusRate: number | null;
|
||||
benefits: RankBenefits;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateRankDto {
|
||||
structureId: string;
|
||||
name: string;
|
||||
level: number;
|
||||
badgeUrl?: string;
|
||||
color?: string;
|
||||
requirements?: RankRequirements;
|
||||
bonusRate?: number;
|
||||
benefits?: RankBenefits;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export type UpdateRankDto = Partial<CreateRankDto>;
|
||||
|
||||
export interface RankFilters {
|
||||
structureId?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// API Service
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export const ranksApi = {
|
||||
list: async (params?: RankFilters): Promise<Rank[]> => {
|
||||
const response = await api.get<Rank[]>('/mlm/ranks', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
get: async (id: string): Promise<Rank> => {
|
||||
const response = await api.get<Rank>(`/mlm/ranks/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: CreateRankDto): Promise<Rank> => {
|
||||
const response = await api.post<Rank>('/mlm/ranks', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string, data: UpdateRankDto): Promise<Rank> => {
|
||||
const response = await api.patch<Rank>(`/mlm/ranks/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
delete: async (id: string): Promise<void> => {
|
||||
await api.delete(`/mlm/ranks/${id}`);
|
||||
},
|
||||
|
||||
evaluate: async (structureId: string): Promise<number> => {
|
||||
const response = await api.post<number>('/mlm/ranks/evaluate', null, {
|
||||
params: { structureId },
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
82
src/services/mlm/structures.api.ts
Normal file
82
src/services/mlm/structures.api.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import api from '../api';
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Types
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export type StructureType = 'unilevel' | 'binary' | 'matrix' | 'hybrid';
|
||||
|
||||
export interface LevelRate {
|
||||
level: number;
|
||||
rate: number;
|
||||
}
|
||||
|
||||
export interface StructureConfig {
|
||||
maxWidth?: number | null;
|
||||
maxDepth?: number;
|
||||
spillover?: 'left_first' | 'weak_leg' | 'balanced';
|
||||
width?: number;
|
||||
depth?: number;
|
||||
}
|
||||
|
||||
export interface Structure {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
type: StructureType;
|
||||
config: StructureConfig;
|
||||
levelRates: LevelRate[];
|
||||
matchingRates: LevelRate[];
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface CreateStructureDto {
|
||||
name: string;
|
||||
description?: string;
|
||||
type: StructureType;
|
||||
config?: StructureConfig;
|
||||
levelRates?: LevelRate[];
|
||||
matchingRates?: LevelRate[];
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export type UpdateStructureDto = Partial<CreateStructureDto>;
|
||||
|
||||
export interface StructureFilters {
|
||||
type?: StructureType;
|
||||
isActive?: boolean;
|
||||
search?: string;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// API Service
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
export const structuresApi = {
|
||||
list: async (params?: StructureFilters): Promise<Structure[]> => {
|
||||
const response = await api.get<Structure[]>('/mlm/structures', { params });
|
||||
return response.data;
|
||||
},
|
||||
|
||||
get: async (id: string): Promise<Structure> => {
|
||||
const response = await api.get<Structure>(`/mlm/structures/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
create: async (data: CreateStructureDto): Promise<Structure> => {
|
||||
const response = await api.post<Structure>('/mlm/structures', data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
update: async (id: string, data: UpdateStructureDto): Promise<Structure> => {
|
||||
const response = await api.patch<Structure>(`/mlm/structures/${id}`, data);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
delete: async (id: string): Promise<void> => {
|
||||
await api.delete(`/mlm/structures/${id}`);
|
||||
},
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user