Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/pages/portfoliio/PortfolioPage.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from "react";
import PortfolioHeader from "@/pages/portfoliio/components/PortfolioHeader";
import PortfolioHeader from "@/pages/portfoliio/components/PortfolioHeader";
import PortfolioSummary from "@/pages/portfoliio/components/PortfolioSummary";
import TransactionHistory from "@/pages/portfoliio/components/Transaction/TransactionHistory";

const PortfolioPage = () => {
return (
<div className="min-h-screen bg-bg-2">
<div className="max-w-screen-xl mx-auto px-4 sm:px-16 lg:px-24 py-12">
<PortfolioHeader />
<PortfolioSummary />
<TransactionHistory />
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/pages/portfoliio/components/PortfolioSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const PortfolioSummary = () => {
</div>
</div>

<div className="text-left">
<div className="text-left ml-3">
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-B2-M text-gold-500 flex items-center gap-1"
Expand All @@ -37,7 +37,7 @@ const PortfolioSummary = () => {
<div className="flex justify-start gap-2 pb-2 pt-2">
<button
onClick={() => setSelectedFilter("stock")}
className={`border text-B2-M px-3 py-1 rounded-full ${
className={`bordr text-B2-M px-3 py-1 rounded-full ${
selectedFilter === "stock"
? "bg-gold-300 text-white"
: "border-neutral-200"
Expand Down
104 changes: 104 additions & 0 deletions src/pages/portfoliio/components/Transaction/TransactionFilters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useState, useEffect } from "react";
import { ChevronDown, ChevronUp, X } from "lucide-react";
import { cn } from "@/lib/utils";
import { Category } from "./types/transaction";

interface TransactionFiltersProps {
onFilterChange: (type: "category", value: Category[]) => void;
fCategory: Category[];
isOtherFilterSelected?: boolean; // 기간별 필터가 선택되었는지 알려주는 prop
onTopFilterClick: () => void; // 상위 필터 버튼 클릭 핸들러 추가
selectedFilter: "stock" | "period"; // 현재 선택된 필터 상태 추가
}

const categories: Category[] = [
"AI 딥러닝 트레이딩",
"알고리즘 트레이딩",
"Test 트레이딩",
];

export default function TransactionFilters({
onFilterChange,
fCategory,
isOtherFilterSelected,
onTopFilterClick, // props로 받음
selectedFilter, // props로 받음
}: TransactionFiltersProps) {
const [open, setOpen] = useState(false);

useEffect(() => {
if (isOtherFilterSelected) {
setOpen(false);
onFilterChange("category", []);
}
}, [isOtherFilterSelected, onFilterChange]);

const handleToggleCategory = (category: Category) => {
const isSelected = fCategory.includes(category);
if (isSelected) {
onFilterChange(
"category",
fCategory.filter((c) => c !== category)
);
} else {
onFilterChange("category", [...fCategory, category]);
}
};

const handleClear = () => {
onFilterChange("category", []);
};

return (
<div className="relative inline-block text-left">
<button
onClick={() => {
onTopFilterClick(); // 상위 필터 클릭 함수 호출
setOpen((prev) => !prev);
}}
className={cn(
"border text-sm px-3 py-1 rounded-full",
selectedFilter === "stock"
? "bg-gold-300 text-white"
: "border-neutral-200 text-gray-700 hover:bg-neutral-50"
)}
>
<span>종목별</span>
</button>

{open && (
<div className="absolute z-10 mt-2 w-56 rounded-lg border border-gray-200 bg-white p-2 shadow-md">
<div className="flex items-center justify-between px-2 py-1">
<span className="text-xs text-gray-500">종목</span>
<button
onClick={handleClear}
className="text-xs text-gray-400 hover:text-black flex items-center gap-1"
>
<X size={12} /> 초기화
</button>
</div>
<div className="grid grid-cols-1 gap-1 max-h-56 overflow-auto p-1">
{categories.map((category) => (
<button
key={category}
onClick={() => handleToggleCategory(category)}
className={cn(
"text-left px-3 py-2 rounded-md hover:bg-neutral-50 text-sm flex items-center",
fCategory.includes(category) && "bg-yellow-50 border border-yellow-200"
)}
>
<input
type="checkbox"
checked={fCategory.includes(category)}
onChange={() => {}}
className="mr-2 rounded-sm"
/>
{category}
</button>
))}
</div>
</div>
)}
</div>
);
}
186 changes: 186 additions & 0 deletions src/pages/portfoliio/components/Transaction/TransactionHistory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import React, { useState, useMemo } from "react";
import { ChevronDown, ChevronUp } from "lucide-react";
import { cn } from "@/lib/utils";
import TransactionTable from "./TransactionTable";
import TransactionFilters from "./TransactionFilters";
import {
Transaction,
SortKey,
SortDir,
Category,
OrderType,
TradeType,
} from "./types/transaction";

/** ---- Mock Data (예시) ---- */
const mockTx: Transaction[] = [
{
id: "T-2025-01-01-001",
date: "2025-01-01",
time: "00:00:00",
category: "AI 딥러닝 트레이딩",
division: "시장가",
type: "체결",
quantity: 1,
price: 850_000,
pnl: 533_840,
side: "매도",
},
{
id: "T-2025-01-01-002",
date: "2025-01-01",
time: "00:10:00",
category: "AI 딥러닝 트레이딩",
division: "지정가",
type: "체결",
quantity: 1,
price: 850_000,
pnl: 533_840,
side: "매도",
},
{
id: "T-2025-01-01-003",
date: "2025-01-01",
time: "00:24:01",
category: "AI 딥러닝 트레이딩",
division: "시장가",
type: "체결",
quantity: 1,
price: 850_000,
pnl: -12_000,
side: "매수",
},
{
id: "T-2025-01-01-004",
date: "2025-01-01",
time: "00:00:8",
category: "AI 딥러닝 트레이딩",
division: "시장가",
type: "미체결",
quantity: 1,
price: 850_000,
pnl: 300,
side: "매수",
},
{
id: "T-2025-01-01-005",
date: "2025-01-01",
time: "00:00:00",
category: "AI 딥러닝 트레이딩",
division: "시장가",
type: "체결",
quantity: 1,
price: 850_000,
pnl: 533_840,
side: "매도",
},
];

export default function TransactionHistory() {
const [data] = useState<Transaction[]>(mockTx);
const [isExpanded, setIsExpanded] = useState(false);
const [selectedFilter, setSelectedFilter] = useState<"stock" | "period">("stock");

const [fCategory, setFCategory] = useState<Category[]>([]);
const [sortKey, setSortKey] = useState<SortKey>("date");
const [sortDir, setSortDir] = useState<SortDir>("desc");
const [page, setPage] = useState(1);
const PAGE_SIZE = 10;

const filtered = useMemo(() => {
if (selectedFilter === "stock" && fCategory.length > 0) {
return data.filter((d) => fCategory.includes(d.category));
}
return data;
}, [data, fCategory, selectedFilter]);

const sorted = useMemo(() => {
const arr = [...filtered];
arr.sort((a, b) => {
const va = String(a[sortKey]);
const vb = String(b[sortKey]);
if (va === vb) return 0;
return sortDir === "asc" ? (va < vb ? -1 : 1) : va > vb ? -1 : 1;
});
return arr;
}, [filtered, sortKey, sortDir]);

const total = sorted.length;
const paged = sorted.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE);

const toggleLabel = isExpanded ? "간단히 보기" : "자세히 보기";
const Icon = isExpanded ? ChevronUp : ChevronDown;

const toggleSort = (key: SortKey) => {
if (sortKey !== key) {
setSortKey(key);
setSortDir("asc");
} else {
setSortDir((d) => (d === "asc" ? "desc" : "asc"));
}
};

const handleCategoryFilterChange = (type: "category", value: Category[]) => {
setFCategory(value);
};

const handleTopFilterChange = (filter: "stock" | "period") => {
setSelectedFilter(filter);
if (filter === "period") {
setFCategory([]);
}
};

return (
<div className="mt-20">
<h2 className="text-xl ml-2 font-semibold">거래내역</h2>

<section className="bg-white rounded-md border border-neutral-200 p-8 mt-3 space-y-4">
<div className="flex flex-col p-3">
<p className="text-T2-B text-text-default">총 {total}회</p>
<div className="mt-5">
<button
onClick={() => setIsExpanded(!isExpanded)}
className="text-B2-M text-gold-500 flex items-center gap-1"
>
<Icon size={16} />
{toggleLabel}
</button>
</div>
</div>


{isExpanded && (
<>
<div className="flex justify-start gap-2 pb-2 pt-2">
<TransactionFilters
fCategory={fCategory}
onFilterChange={handleCategoryFilterChange}
isOtherFilterSelected={selectedFilter === "period"}
onTopFilterClick={() => handleTopFilterChange("stock")}
selectedFilter={selectedFilter}
/>
<button
onClick={() => handleTopFilterChange("period")}
className={cn(
"border text-sm px-3 py-1 rounded-full",
selectedFilter === "period"
? "bg-gold-300 text-white"
: "border-neutral-200"
)}
>
기간별
</button>
</div>
<TransactionTable
data={paged}
sortKey={sortKey}
sortDir={sortDir}
onSort={toggleSort}
/>
</>
)}
</section>
</div>
);
}
Loading