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 (
+
+ );
+}
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')}