From ad196164eee6c30a30f9d195787fa27beb6307ad Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 23:16:20 +0300 Subject: [PATCH] refactor: extract AddPositionForm to features layer --- .../add-position/api/useAddPosition.ts | 6 + .../src/features/add-position/index.ts | 1 + .../add-position/model/useAddPositionForm.ts | 30 ++++ .../add-position/ui/AddPositionForm.tsx | 111 +++++++++++++++ .../portfolios/ui/PortfolioDetailPage.tsx | 134 +----------------- 5 files changed, 151 insertions(+), 131 deletions(-) create mode 100644 apps/frontend/src/features/add-position/api/useAddPosition.ts create mode 100644 apps/frontend/src/features/add-position/index.ts create mode 100644 apps/frontend/src/features/add-position/model/useAddPositionForm.ts create mode 100644 apps/frontend/src/features/add-position/ui/AddPositionForm.tsx diff --git a/apps/frontend/src/features/add-position/api/useAddPosition.ts b/apps/frontend/src/features/add-position/api/useAddPosition.ts new file mode 100644 index 0000000..1171003 --- /dev/null +++ b/apps/frontend/src/features/add-position/api/useAddPosition.ts @@ -0,0 +1,6 @@ +import { usePositionMutations } from '@/entities/portfolio'; + +export function useAddPosition(portfolioId: number) { + const { add } = usePositionMutations(portfolioId); + return add; +} diff --git a/apps/frontend/src/features/add-position/index.ts b/apps/frontend/src/features/add-position/index.ts new file mode 100644 index 0000000..9c5f41a --- /dev/null +++ b/apps/frontend/src/features/add-position/index.ts @@ -0,0 +1 @@ +export { AddPositionForm } from './ui/AddPositionForm'; diff --git a/apps/frontend/src/features/add-position/model/useAddPositionForm.ts b/apps/frontend/src/features/add-position/model/useAddPositionForm.ts new file mode 100644 index 0000000..7b3b650 --- /dev/null +++ b/apps/frontend/src/features/add-position/model/useAddPositionForm.ts @@ -0,0 +1,30 @@ +import { useState } from 'react'; + +export function useAddPositionForm() { + const [showAddForm, setShowAddForm] = useState(false); + const [newSecid, setNewSecid] = useState(''); + const [newQty, setNewQty] = useState('1'); + const [newPrice, setNewPrice] = useState(''); + const [newDate, setNewDate] = useState(new Date().toISOString().split('T')[0]); + + function reset() { + setNewSecid(''); + setNewQty('1'); + setNewPrice(''); + setNewDate(new Date().toISOString().split('T')[0]); + } + + return { + showAddForm, + setShowAddForm, + newSecid, + setNewSecid, + newQty, + setNewQty, + newPrice, + setNewPrice, + newDate, + setNewDate, + reset, + }; +} diff --git a/apps/frontend/src/features/add-position/ui/AddPositionForm.tsx b/apps/frontend/src/features/add-position/ui/AddPositionForm.tsx new file mode 100644 index 0000000..ed57feb --- /dev/null +++ b/apps/frontend/src/features/add-position/ui/AddPositionForm.tsx @@ -0,0 +1,111 @@ +import { useAddPosition } from '../api/useAddPosition'; +import { useAddPositionForm } from '../model/useAddPositionForm'; + +const inputStyle: React.CSSProperties = { + padding: '8px 12px', + border: '1px solid #e0e0e0', + borderRadius: 'var(--border-radius)', + fontSize: 14, +}; + +export function AddPositionForm({ portfolioId }: { portfolioId: number }) { + const addPosition = useAddPosition(portfolioId); + const form = useAddPositionForm(); + + function handleAddPosition() { + if (!form.newSecid.trim() || !parseInt(form.newQty, 10)) return; + addPosition.mutate( + { + secid: form.newSecid.trim().toUpperCase(), + quantity: parseInt(form.newQty, 10), + buyPrice: form.newPrice ? parseFloat(form.newPrice) : undefined, + buyDate: form.newDate || undefined, + }, + { + onSuccess: () => { + form.setShowAddForm(false); + form.reset(); + }, + }, + ); + } + + return ( +
+
+ + form.setNewSecid(e.target.value)} + placeholder="SBER" + style={{ ...inputStyle, width: 120 }} + /> +
+
+ + form.setNewQty(e.target.value)} + style={{ ...inputStyle, width: 100 }} + /> +
+
+ + form.setNewPrice(e.target.value)} + placeholder="0.00" + style={{ ...inputStyle, width: 120 }} + /> +
+
+ + form.setNewDate(e.target.value)} + style={{ ...inputStyle, width: 150 }} + /> +
+ +
+ ); +} diff --git a/apps/frontend/src/pages/portfolios/ui/PortfolioDetailPage.tsx b/apps/frontend/src/pages/portfolios/ui/PortfolioDetailPage.tsx index 9ec841f..6387f4d 100644 --- a/apps/frontend/src/pages/portfolios/ui/PortfolioDetailPage.tsx +++ b/apps/frontend/src/pages/portfolios/ui/PortfolioDetailPage.tsx @@ -6,6 +6,7 @@ import { PortfolioSummary } from '@/widgets/portfolio-summary'; import { AnalyticsSummary } from '@/widgets/portfolio-analytics'; import { SharePositionTable } from '@/widgets/share-positions-table'; import { BondPositionTable } from '@/widgets/bond-positions-table'; +import { AddPositionForm } from '@/features/add-position'; export function PortfolioDetailPage() { const { id } = useParams<{ id: string }>(); @@ -13,18 +14,10 @@ export function PortfolioDetailPage() { const { data: portfolio, isLoading, error } = usePortfolio(portfolioId); const { update, remove } = usePortfolioMutations(); - const { - update: updatePosition, - remove: removePosition, - add: addPosition, - } = usePositionMutations(portfolioId); + const { update: updatePosition, remove: removePosition } = usePositionMutations(portfolioId); const [editing, setEditing] = useState(false); const [showAddForm, setShowAddForm] = useState(false); - const [newSecid, setNewSecid] = useState(''); - const [newQty, setNewQty] = useState('1'); - const [newPrice, setNewPrice] = useState(''); - const [newDate, setNewDate] = useState(new Date().toISOString().split('T')[0]); if (isLoading) { return ( @@ -48,26 +41,6 @@ export function PortfolioDetailPage() { } } - function handleAddPosition() { - if (!newSecid.trim() || !parseInt(newQty, 10)) return; - addPosition.mutate( - { - secid: newSecid.trim().toUpperCase(), - quantity: parseInt(newQty, 10), - buyPrice: newPrice ? parseFloat(newPrice) : undefined, - buyDate: newDate || undefined, - }, - { - onSuccess: () => { - setShowAddForm(false); - setNewSecid(''); - setNewQty('1'); - setNewPrice(''); - }, - }, - ); - } - return (
- {showAddForm && ( -
-
- - setNewSecid(e.target.value)} - placeholder="SBER" - style={{ - padding: '8px 12px', - border: '1px solid #e0e0e0', - borderRadius: 'var(--border-radius)', - fontSize: 14, - width: 120, - }} - /> -
-
- - setNewQty(e.target.value)} - style={{ - padding: '8px 12px', - border: '1px solid #e0e0e0', - borderRadius: 'var(--border-radius)', - fontSize: 14, - width: 100, - }} - /> -
-
- - setNewPrice(e.target.value)} - placeholder="0.00" - style={{ - padding: '8px 12px', - border: '1px solid #e0e0e0', - borderRadius: 'var(--border-radius)', - fontSize: 14, - width: 120, - }} - /> -
-
- - setNewDate(e.target.value)} - style={{ - padding: '8px 12px', - border: '1px solid #e0e0e0', - borderRadius: 'var(--border-radius)', - fontSize: 14, - width: 150, - }} - /> -
- -
- )} + {showAddForm && } p.type === 'share')}