refactor(frontend): migrate entities/portfolio to FSD
This commit is contained in:
parent
9c115f0e16
commit
946383bde0
@ -1,93 +1,11 @@
|
|||||||
import { request } from './client';
|
export {
|
||||||
import type { AnalyticsResponse, Portfolio, PortfolioDetail, Position } from './responses';
|
getPortfolios,
|
||||||
|
getPortfolio,
|
||||||
export function getPortfolios(): Promise<{
|
createPortfolio,
|
||||||
data: Portfolio[];
|
updatePortfolio,
|
||||||
meta: { cachedAt: string | null; fromCache: boolean };
|
deletePortfolio,
|
||||||
}> {
|
addPosition,
|
||||||
return request<Portfolio[]>('/api/v1/portfolios');
|
updatePosition,
|
||||||
}
|
removePosition,
|
||||||
|
getPortfolioAnalytics,
|
||||||
export function getPortfolio(
|
} from '../entities/portfolio/api/portfolioApi';
|
||||||
id: number,
|
|
||||||
): Promise<{ data: PortfolioDetail; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<PortfolioDetail>(`/api/v1/portfolios/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createPortfolio(data: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
currency?: string;
|
|
||||||
}): Promise<{ data: Portfolio; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<Portfolio>('/api/v1/portfolios', undefined, {
|
|
||||||
method: 'POST',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updatePortfolio(
|
|
||||||
id: number,
|
|
||||||
data: { name?: string; description?: string; currency?: string },
|
|
||||||
): Promise<{ data: Portfolio; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<Portfolio>(`/api/v1/portfolios/${id}`, undefined, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deletePortfolio(
|
|
||||||
id: number,
|
|
||||||
): Promise<{ data: null; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<null>(`/api/v1/portfolios/${id}`, undefined, {
|
|
||||||
method: 'DELETE',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addPosition(
|
|
||||||
portfolioId: number,
|
|
||||||
data: {
|
|
||||||
secid: string;
|
|
||||||
quantity: number;
|
|
||||||
buyPrice?: number;
|
|
||||||
buyDate?: string;
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
},
|
|
||||||
): Promise<{ data: Position; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<Position>(`/api/v1/portfolios/${portfolioId}/positions`, undefined, {
|
|
||||||
method: 'POST',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updatePosition(
|
|
||||||
portfolioId: number,
|
|
||||||
positionId: number,
|
|
||||||
data: {
|
|
||||||
quantity?: number;
|
|
||||||
buyPrice?: number;
|
|
||||||
buyDate?: string;
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
},
|
|
||||||
): Promise<{ data: Position; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<Position>(`/api/v1/portfolios/${portfolioId}/positions/${positionId}`, undefined, {
|
|
||||||
method: 'PATCH',
|
|
||||||
body: data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function removePosition(
|
|
||||||
portfolioId: number,
|
|
||||||
positionId: number,
|
|
||||||
): Promise<{ data: null; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<null>(`/api/v1/portfolios/${portfolioId}/positions/${positionId}`, undefined, {
|
|
||||||
method: 'DELETE',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPortfolioAnalytics(
|
|
||||||
portfolioId: number,
|
|
||||||
): Promise<{ data: AnalyticsResponse; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
|
||||||
return request<AnalyticsResponse>(`/api/v1/portfolios/${portfolioId}/analytics`);
|
|
||||||
}
|
|
||||||
|
|||||||
98
apps/frontend/src/entities/portfolio/api/portfolioApi.ts
Normal file
98
apps/frontend/src/entities/portfolio/api/portfolioApi.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
import { request } from '@/shared/api/client';
|
||||||
|
import type {
|
||||||
|
AnalyticsResponse,
|
||||||
|
Portfolio,
|
||||||
|
PortfolioDetail,
|
||||||
|
Position,
|
||||||
|
} from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function getPortfolios(): Promise<{
|
||||||
|
data: Portfolio[];
|
||||||
|
meta: { cachedAt: string | null; fromCache: boolean };
|
||||||
|
}> {
|
||||||
|
return request<Portfolio[]>('/api/v1/portfolios');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPortfolio(
|
||||||
|
id: number,
|
||||||
|
): Promise<{ data: PortfolioDetail; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<PortfolioDetail>(`/api/v1/portfolios/${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPortfolio(data: {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
currency?: string;
|
||||||
|
}): Promise<{ data: Portfolio; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<Portfolio>('/api/v1/portfolios', undefined, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePortfolio(
|
||||||
|
id: number,
|
||||||
|
data: { name?: string; description?: string; currency?: string },
|
||||||
|
): Promise<{ data: Portfolio; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<Portfolio>(`/api/v1/portfolios/${id}`, undefined, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deletePortfolio(
|
||||||
|
id: number,
|
||||||
|
): Promise<{ data: null; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<null>(`/api/v1/portfolios/${id}`, undefined, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addPosition(
|
||||||
|
portfolioId: number,
|
||||||
|
data: {
|
||||||
|
secid: string;
|
||||||
|
quantity: number;
|
||||||
|
buyPrice?: number;
|
||||||
|
buyDate?: string;
|
||||||
|
notes?: string;
|
||||||
|
tags?: string[];
|
||||||
|
},
|
||||||
|
): Promise<{ data: Position; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<Position>(`/api/v1/portfolios/${portfolioId}/positions`, undefined, {
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updatePosition(
|
||||||
|
portfolioId: number,
|
||||||
|
positionId: number,
|
||||||
|
data: {
|
||||||
|
quantity?: number;
|
||||||
|
buyPrice?: number;
|
||||||
|
buyDate?: string;
|
||||||
|
notes?: string;
|
||||||
|
tags?: string[];
|
||||||
|
},
|
||||||
|
): Promise<{ data: Position; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<Position>(`/api/v1/portfolios/${portfolioId}/positions/${positionId}`, undefined, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removePosition(
|
||||||
|
portfolioId: number,
|
||||||
|
positionId: number,
|
||||||
|
): Promise<{ data: null; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<null>(`/api/v1/portfolios/${portfolioId}/positions/${positionId}`, undefined, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPortfolioAnalytics(
|
||||||
|
portfolioId: number,
|
||||||
|
): Promise<{ data: AnalyticsResponse; meta: { cachedAt: string | null; fromCache: boolean } }> {
|
||||||
|
return request<AnalyticsResponse>(`/api/v1/portfolios/${portfolioId}/analytics`);
|
||||||
|
}
|
||||||
16
apps/frontend/src/entities/portfolio/index.ts
Normal file
16
apps/frontend/src/entities/portfolio/index.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export { usePortfolio } from './model/usePortfolio';
|
||||||
|
export { usePortfolios } from './model/usePortfolios';
|
||||||
|
export { usePortfolioAnalytics } from './model/usePortfolioAnalytics';
|
||||||
|
export { usePortfolioMutations } from './model/usePortfolioMutations';
|
||||||
|
export { usePositionMutations } from './model/usePositionMutations';
|
||||||
|
export {
|
||||||
|
getPortfolios,
|
||||||
|
getPortfolio,
|
||||||
|
createPortfolio,
|
||||||
|
updatePortfolio,
|
||||||
|
deletePortfolio,
|
||||||
|
addPosition,
|
||||||
|
updatePosition,
|
||||||
|
removePosition,
|
||||||
|
getPortfolioAnalytics,
|
||||||
|
} from './api/portfolioApi';
|
||||||
17
apps/frontend/src/entities/portfolio/model/usePortfolio.ts
Normal file
17
apps/frontend/src/entities/portfolio/model/usePortfolio.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getPortfolio } from '../api/portfolioApi';
|
||||||
|
import type { PortfolioDetail } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function usePortfolio(id: number) {
|
||||||
|
return useQuery<PortfolioDetail>({
|
||||||
|
queryKey: ['portfolio', id],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getPortfolio(id);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 900_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
enabled: !!id,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getPortfolioAnalytics } from '../api/portfolioApi';
|
||||||
|
import type { AnalyticsResponse } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function usePortfolioAnalytics(portfolioId: number) {
|
||||||
|
return useQuery<AnalyticsResponse>({
|
||||||
|
queryKey: ['portfolio', portfolioId, 'analytics'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getPortfolioAnalytics(portfolioId);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 900_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
enabled: !!portfolioId,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { createPortfolio, updatePortfolio, deletePortfolio } from '../api/portfolioApi';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function usePortfolioMutations() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const create = useMutation({
|
||||||
|
mutationFn: (data: { name: string; description?: string; currency?: string }) =>
|
||||||
|
createPortfolio(data),
|
||||||
|
onSuccess: (res) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
||||||
|
navigate(`/portfolios/${res.data.id}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
id,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
id: number;
|
||||||
|
data: {
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
currency?: string;
|
||||||
|
};
|
||||||
|
}) => updatePortfolio(id, data),
|
||||||
|
onSuccess: (_, { id }) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolio', id] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const remove = useMutation({
|
||||||
|
mutationFn: (id: number) => deletePortfolio(id),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
||||||
|
navigate('/portfolios');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { create, update, remove };
|
||||||
|
}
|
||||||
16
apps/frontend/src/entities/portfolio/model/usePortfolios.ts
Normal file
16
apps/frontend/src/entities/portfolio/model/usePortfolios.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getPortfolios } from '../api/portfolioApi';
|
||||||
|
import type { Portfolio } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function usePortfolios() {
|
||||||
|
return useQuery<Portfolio[]>({
|
||||||
|
queryKey: ['portfolios'],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getPortfolios();
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 900_000,
|
||||||
|
retry: 2,
|
||||||
|
refetchOnWindowFocus: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { addPosition, updatePosition, removePosition } from '../api/portfolioApi';
|
||||||
|
import type { PortfolioDetail } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function usePositionMutations(portfolioId: number) {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const add = useMutation({
|
||||||
|
mutationFn: (data: {
|
||||||
|
secid: string;
|
||||||
|
quantity: number;
|
||||||
|
buyPrice?: number;
|
||||||
|
buyDate?: string;
|
||||||
|
notes?: string;
|
||||||
|
tags?: string[];
|
||||||
|
}) => addPosition(portfolioId, data),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const update = useMutation({
|
||||||
|
mutationFn: ({
|
||||||
|
positionId,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
positionId: number;
|
||||||
|
data: {
|
||||||
|
quantity?: number;
|
||||||
|
buyPrice?: number;
|
||||||
|
buyDate?: string;
|
||||||
|
notes?: string;
|
||||||
|
tags?: string[];
|
||||||
|
};
|
||||||
|
}) => updatePosition(portfolioId, positionId, data),
|
||||||
|
onMutate: async ({ positionId, data }) => {
|
||||||
|
await queryClient.cancelQueries({ queryKey: ['portfolio', portfolioId] });
|
||||||
|
const previous = queryClient.getQueryData<{ data: PortfolioDetail }>([
|
||||||
|
'portfolio',
|
||||||
|
portfolioId,
|
||||||
|
]);
|
||||||
|
queryClient.setQueryData(['portfolio', portfolioId], (old: any) => {
|
||||||
|
if (!old) return old;
|
||||||
|
return {
|
||||||
|
...old,
|
||||||
|
positions: old.positions.map((p: any) =>
|
||||||
|
p.id === positionId
|
||||||
|
? {
|
||||||
|
...p,
|
||||||
|
...(data.quantity !== undefined ? { quantity: data.quantity } : {}),
|
||||||
|
...(data.buyPrice !== undefined ? { buyPrice: data.buyPrice } : {}),
|
||||||
|
...(data.buyDate !== undefined ? { buyDate: data.buyDate } : {}),
|
||||||
|
}
|
||||||
|
: p,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return { previous };
|
||||||
|
},
|
||||||
|
onError: (_err, _vars, context) => {
|
||||||
|
if (context?.previous) {
|
||||||
|
queryClient.setQueryData(['portfolio', portfolioId], context.previous);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSettled: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const remove = useMutation({
|
||||||
|
mutationFn: (positionId: number) => removePosition(portfolioId, positionId),
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { add, update, remove };
|
||||||
|
}
|
||||||
@ -1,17 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { usePortfolio } from '../entities/portfolio/model/usePortfolio';
|
||||||
import { getPortfolio } from '../api/portfolio';
|
|
||||||
import type { PortfolioDetail } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function usePortfolio(id: number) {
|
|
||||||
return useQuery<PortfolioDetail>({
|
|
||||||
queryKey: ['portfolio', id],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getPortfolio(id);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 900_000,
|
|
||||||
retry: 2,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
enabled: !!id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,17 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { usePortfolioAnalytics } from '../entities/portfolio/model/usePortfolioAnalytics';
|
||||||
import { getPortfolioAnalytics } from '../api/portfolio';
|
|
||||||
import type { AnalyticsResponse } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function usePortfolioAnalytics(portfolioId: number) {
|
|
||||||
return useQuery<AnalyticsResponse>({
|
|
||||||
queryKey: ['portfolio', portfolioId, 'analytics'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getPortfolioAnalytics(portfolioId);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 900_000,
|
|
||||||
retry: 2,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
enabled: !!portfolioId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,45 +1 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
export { usePortfolioMutations } from '../entities/portfolio/model/usePortfolioMutations';
|
||||||
import { createPortfolio, updatePortfolio, deletePortfolio } from '../api/portfolio';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
|
|
||||||
export function usePortfolioMutations() {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const create = useMutation({
|
|
||||||
mutationFn: (data: { name: string; description?: string; currency?: string }) =>
|
|
||||||
createPortfolio(data),
|
|
||||||
onSuccess: (res) => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
|
||||||
navigate(`/portfolios/${res.data.id}`);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const update = useMutation({
|
|
||||||
mutationFn: ({
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
id: number;
|
|
||||||
data: {
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
currency?: string;
|
|
||||||
};
|
|
||||||
}) => updatePortfolio(id, data),
|
|
||||||
onSuccess: (_, { id }) => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolio', id] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const remove = useMutation({
|
|
||||||
mutationFn: (id: number) => deletePortfolio(id),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
|
|
||||||
navigate('/portfolios');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { create, update, remove };
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,16 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { usePortfolios } from '../entities/portfolio/model/usePortfolios';
|
||||||
import { getPortfolios } from '../api/portfolio';
|
|
||||||
import type { Portfolio } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function usePortfolios() {
|
|
||||||
return useQuery<Portfolio[]>({
|
|
||||||
queryKey: ['portfolios'],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getPortfolios();
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 900_000,
|
|
||||||
retry: 2,
|
|
||||||
refetchOnWindowFocus: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,78 +1 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
export { usePositionMutations } from '../entities/portfolio/model/usePositionMutations';
|
||||||
import { addPosition, updatePosition, removePosition } from '../api/portfolio';
|
|
||||||
import type { PortfolioDetail } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function usePositionMutations(portfolioId: number) {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
|
|
||||||
const add = useMutation({
|
|
||||||
mutationFn: (data: {
|
|
||||||
secid: string;
|
|
||||||
quantity: number;
|
|
||||||
buyPrice?: number;
|
|
||||||
buyDate?: string;
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
}) => addPosition(portfolioId, data),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const update = useMutation({
|
|
||||||
mutationFn: ({
|
|
||||||
positionId,
|
|
||||||
data,
|
|
||||||
}: {
|
|
||||||
positionId: number;
|
|
||||||
data: {
|
|
||||||
quantity?: number;
|
|
||||||
buyPrice?: number;
|
|
||||||
buyDate?: string;
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
};
|
|
||||||
}) => updatePosition(portfolioId, positionId, data),
|
|
||||||
onMutate: async ({ positionId, data }) => {
|
|
||||||
await queryClient.cancelQueries({ queryKey: ['portfolio', portfolioId] });
|
|
||||||
const previous = queryClient.getQueryData<{ data: PortfolioDetail }>([
|
|
||||||
'portfolio',
|
|
||||||
portfolioId,
|
|
||||||
]);
|
|
||||||
queryClient.setQueryData(['portfolio', portfolioId], (old: any) => {
|
|
||||||
if (!old) return old;
|
|
||||||
return {
|
|
||||||
...old,
|
|
||||||
positions: old.positions.map((p: any) =>
|
|
||||||
p.id === positionId
|
|
||||||
? {
|
|
||||||
...p,
|
|
||||||
...(data.quantity !== undefined ? { quantity: data.quantity } : {}),
|
|
||||||
...(data.buyPrice !== undefined ? { buyPrice: data.buyPrice } : {}),
|
|
||||||
...(data.buyDate !== undefined ? { buyDate: data.buyDate } : {}),
|
|
||||||
}
|
|
||||||
: p,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return { previous };
|
|
||||||
},
|
|
||||||
onError: (_err, _vars, context) => {
|
|
||||||
if (context?.previous) {
|
|
||||||
queryClient.setQueryData(['portfolio', portfolioId], context.previous);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSettled: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const remove = useMutation({
|
|
||||||
mutationFn: (positionId: number) => removePosition(portfolioId, positionId),
|
|
||||||
onSuccess: () => {
|
|
||||||
queryClient.invalidateQueries({ queryKey: ['portfolio', portfolioId] });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return { add, update, remove };
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams, Link } from 'react-router-dom';
|
import { useParams, Link } from 'react-router-dom';
|
||||||
import { usePortfolio } from '../../hooks/usePortfolio';
|
import {
|
||||||
import { usePortfolioMutations } from '../../hooks/usePortfolioMutations';
|
usePortfolio,
|
||||||
import { usePositionMutations } from '../../hooks/usePositionMutations';
|
usePortfolioMutations,
|
||||||
|
usePositionMutations,
|
||||||
|
} from '../../entities/portfolio';
|
||||||
import { PortfolioForm } from '../../components/portfolios/PortfolioForm';
|
import { PortfolioForm } from '../../components/portfolios/PortfolioForm';
|
||||||
import { PortfolioSummary } from '../../components/portfolios/PortfolioSummary';
|
import { PortfolioSummary } from '../../components/portfolios/PortfolioSummary';
|
||||||
import { AnalyticsSummary } from '../../components/portfolios/AnalyticsSummary';
|
import { AnalyticsSummary } from '../../components/portfolios/AnalyticsSummary';
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { usePortfolios } from '../../hooks/usePortfolios';
|
import { usePortfolios, usePortfolioMutations } from '../../entities/portfolio';
|
||||||
import { usePortfolioMutations } from '../../hooks/usePortfolioMutations';
|
|
||||||
import { PortfolioCard } from '../../components/portfolios/PortfolioCard';
|
import { PortfolioCard } from '../../components/portfolios/PortfolioCard';
|
||||||
import { PortfolioForm } from '../../components/portfolios/PortfolioForm';
|
import { PortfolioForm } from '../../components/portfolios/PortfolioForm';
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user