mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
feat(referral-network): кнопка построения полной реферальной сети
Со времён редизайна со скоуп-селектором граф строится только для выбранных кампаний, партнёров или пользователей, и посмотреть сеть целиком стало нечем. Бэкенд для этого уже есть — GET /cabinet/admin/ referral-network/ отдаёт полный граф и остался неиспользуемым после того, как фронт перешёл на /scoped. Возвращаем к нему доступ: в дропдауне селектора появляется закреплённый пункт «Вся сеть», который грузит граф целиком. Режим взаимоисключающий с выбором чипов — полный граф и так содержит все сущности.
This commit is contained in:
105
src/store/referralNetwork.test.ts
Normal file
105
src/store/referralNetwork.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { MAX_SCOPE_ITEMS, useReferralNetworkStore } from './referralNetwork';
|
||||
import type { ScopeSelection } from '@/types/referralNetwork';
|
||||
|
||||
const INITIAL_STATE = useReferralNetworkStore.getState();
|
||||
|
||||
function campaign(id: number): ScopeSelection {
|
||||
return { type: 'campaign', id, label: `Campaign ${id}` };
|
||||
}
|
||||
|
||||
describe('referralNetwork store', () => {
|
||||
beforeEach(() => {
|
||||
useReferralNetworkStore.setState(INITIAL_STATE, true);
|
||||
});
|
||||
|
||||
it('starts with no scope and full network off', () => {
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.scope).toEqual([]);
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
});
|
||||
|
||||
it('drops the selected scope when full network is enabled', () => {
|
||||
const { addScope, setFullNetwork } = useReferralNetworkStore.getState();
|
||||
addScope(campaign(1));
|
||||
addScope(campaign(2));
|
||||
|
||||
setFullNetwork(true);
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.isFullNetwork).toBe(true);
|
||||
expect(state.scope).toEqual([]);
|
||||
});
|
||||
|
||||
it('leaves full network mode when a scope item is added', () => {
|
||||
const { setFullNetwork, addScope } = useReferralNetworkStore.getState();
|
||||
setFullNetwork(true);
|
||||
|
||||
addScope(campaign(1));
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
expect(state.scope).toEqual([campaign(1)]);
|
||||
});
|
||||
|
||||
it('resets the graph selection when switching to full network', () => {
|
||||
const { setSelectedNode, setHighlightedNodes, setFullNetwork } =
|
||||
useReferralNetworkStore.getState();
|
||||
setSelectedNode({ type: 'user', id: 7 });
|
||||
setHighlightedNodes(new Set(['user_7']));
|
||||
|
||||
setFullNetwork(true);
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.selectedNode).toBeNull();
|
||||
expect(state.highlightedNodes.size).toBe(0);
|
||||
});
|
||||
|
||||
it('turns full network off without leaving a stale scope', () => {
|
||||
const { setFullNetwork } = useReferralNetworkStore.getState();
|
||||
setFullNetwork(true);
|
||||
|
||||
setFullNetwork(false);
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
expect(state.scope).toEqual([]);
|
||||
});
|
||||
|
||||
it('clears both the scope and full network mode', () => {
|
||||
const { setFullNetwork, clearScope } = useReferralNetworkStore.getState();
|
||||
setFullNetwork(true);
|
||||
|
||||
clearScope();
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
expect(state.scope).toEqual([]);
|
||||
});
|
||||
|
||||
it('ignores duplicate scope items and keeps full network off', () => {
|
||||
const { addScope } = useReferralNetworkStore.getState();
|
||||
addScope(campaign(1));
|
||||
addScope(campaign(1));
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.scope).toEqual([campaign(1)]);
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
});
|
||||
|
||||
it('does not exit full network mode when the scope is already full', () => {
|
||||
const { addScope, setFullNetwork } = useReferralNetworkStore.getState();
|
||||
for (let id = 1; id <= MAX_SCOPE_ITEMS; id++) {
|
||||
addScope(campaign(id));
|
||||
}
|
||||
setFullNetwork(true);
|
||||
|
||||
// The cap is per-scope; with the scope dropped by full-network mode there is
|
||||
// nothing to reject, so this add must switch the mode off and take effect.
|
||||
addScope(campaign(999));
|
||||
|
||||
const state = useReferralNetworkStore.getState();
|
||||
expect(state.isFullNetwork).toBe(false);
|
||||
expect(state.scope).toEqual([campaign(999)]);
|
||||
});
|
||||
});
|
||||
@@ -24,12 +24,14 @@ interface ReferralNetworkState {
|
||||
highlightedNodes: Set<string>;
|
||||
filters: NetworkFilters;
|
||||
scope: ScopeSelection[];
|
||||
isFullNetwork: boolean;
|
||||
|
||||
setSelectedNode: (node: SelectedNode) => void;
|
||||
setHoveredNode: (id: string | null) => void;
|
||||
setHighlightedNodes: (nodes: Set<string>) => void;
|
||||
updateFilters: (filters: Partial<NetworkFilters>) => void;
|
||||
resetFilters: () => void;
|
||||
setFullNetwork: (enabled: boolean) => void;
|
||||
addScope: (item: ScopeSelection) => void;
|
||||
removeScope: (type: ScopeSelection['type'], id: number) => void;
|
||||
clearScope: () => void;
|
||||
@@ -41,6 +43,7 @@ export const useReferralNetworkStore = create<ReferralNetworkState>()((set) => (
|
||||
highlightedNodes: new Set<string>(),
|
||||
filters: { ...DEFAULT_FILTERS },
|
||||
scope: [],
|
||||
isFullNetwork: false,
|
||||
|
||||
setSelectedNode: (node) => set({ selectedNode: node }),
|
||||
setHoveredNode: (id) => set({ hoveredNodeId: id }),
|
||||
@@ -53,6 +56,16 @@ export const useReferralNetworkStore = create<ReferralNetworkState>()((set) => (
|
||||
|
||||
resetFilters: () => set({ filters: { ...DEFAULT_FILTERS } }),
|
||||
|
||||
// Full network and a scope selection are mutually exclusive: the whole graph
|
||||
// already contains every campaign, partner and user, so narrowing it by chips
|
||||
// would be meaningless. Each mode therefore drops the other.
|
||||
setFullNetwork: (enabled) =>
|
||||
set({
|
||||
isFullNetwork: enabled,
|
||||
scope: [],
|
||||
...resetGraphState(),
|
||||
}),
|
||||
|
||||
addScope: (item) =>
|
||||
set((state) => {
|
||||
const exists = state.scope.some((s) => s.type === item.type && s.id === item.id);
|
||||
@@ -60,6 +73,7 @@ export const useReferralNetworkStore = create<ReferralNetworkState>()((set) => (
|
||||
if (state.scope.length >= MAX_SCOPE_ITEMS) return state;
|
||||
return {
|
||||
scope: [...state.scope, item],
|
||||
isFullNetwork: false,
|
||||
...resetGraphState(),
|
||||
};
|
||||
}),
|
||||
@@ -76,6 +90,7 @@ export const useReferralNetworkStore = create<ReferralNetworkState>()((set) => (
|
||||
clearScope: () =>
|
||||
set({
|
||||
scope: [],
|
||||
isFullNetwork: false,
|
||||
...resetGraphState(),
|
||||
}),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user