Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b2d563d
Add .gitignore
ShibaInuChan Jun 3, 2026
fdb8b18
Merge branch 'claude/laughing-pascal-1VLvc'
ShibaInuChan Jun 3, 2026
9ccc291
Add vercel.json for SPA routing
ShibaInuChan Jun 3, 2026
b25f002
Fix dashboard: title, chart height, category percentages
ShibaInuChan Jun 3, 2026
b99a7e3
Fix pie chart tooltip: show category name and percentage
ShibaInuChan Jun 3, 2026
5f9af53
Update README
ShibaInuChan Jun 3, 2026
a84c21c
Add pension/iDeCo category
ShibaInuChan Jun 3, 2026
525075d
Add pension category, fix formatJPY to show exact amounts
ShibaInuChan Jun 3, 2026
eed365f
Unify number format: short (150.9万) for summaries, full (1,509,300円) …
ShibaInuChan Jun 3, 2026
d323076
Remove unused formatJPY import in History
ShibaInuChan Jun 3, 2026
e1eb418
Total assets: remove redundant small text, show exact amount only
ShibaInuChan Jun 3, 2026
384acab
Add robo-advisor and gold categories
ShibaInuChan Jun 3, 2026
1c3a250
Remove real estate and insurance categories
ShibaInuChan Jun 3, 2026
e2a9c2a
Remove real estate and insurance from sample data
ShibaInuChan Jun 3, 2026
c62bd2a
Merge stock categories and fund/robo-advisor, add legacy key aliases
ShibaInuChan Jun 3, 2026
7267686
Add drag-and-drop reordering to asset list
ShibaInuChan Jun 3, 2026
2e818aa
Migrate localStorage data: normalize legacy category keys on load
ShibaInuChan Jun 3, 2026
9d8d0c5
Add investment and alternative asset sub-totals to total card
ShibaInuChan Jun 3, 2026
c37a6de
Split total card into traditional, alternative, and total investment …
ShibaInuChan Jun 3, 2026
3bf5f8b
Update README to reflect current categories and features
ShibaInuChan Jun 3, 2026
dbcb8bf
Fix mobile: content padding, modal height, touch drag-and-drop
ShibaInuChan Jun 3, 2026
869a57e
Fix mobile UX: content padding, modal above bottom nav, touch drag in…
ShibaInuChan Jun 3, 2026
38041c3
Fix pie chart: remove active shape outline on click
ShibaInuChan Jun 3, 2026
67b6f5c
Remove blue focus outline from pie chart via CSS and stroke:none
ShibaInuChan Jun 3, 2026
dfd450b
Fix scroll interference: only prevent default after 10px drag threshold
ShibaInuChan Jun 3, 2026
56e821e
Update category colors to MoneyForward-inspired palette
ShibaInuChan Jun 3, 2026
d342d20
Increase pie chart hover expansion to +10px
ShibaInuChan Jun 3, 2026
7e308a7
Update title to 資産管理ダッシュボード
ShibaInuChan Jun 3, 2026
205cc49
Reduce pie chart hover expansion from 10px to 5px (#3)
ShibaInuChan Jun 3, 2026
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
26 changes: 4 additions & 22 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,6 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
node_modules/
dist/
dist-ssr/
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.env
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
92 changes: 33 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,73 +1,47 @@
# React + TypeScript + Vite
# 資産管理アプリ

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
マネーフォワードライクなブラウザ資産管理アプリ。**暗号資産・金などのオルタナティブ資産を伝統的資産と分けて管理**できることが特徴です。

Currently, two official plugins are available:
🌐 **[asset-management-nu-seven.vercel.app](https://asset-management-nu-seven.vercel.app)**

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
## 主な機能

## React Compiler
- **ダッシュボード** — 総資産・伝統的資産/オルタナティブ資産/運用資産合計の内訳・カテゴリ別ドーナツグラフ(割合表示)
- **資産一覧** — カテゴリ別グループ表示・追加/編集/削除・ドラッグ&ドロップで並び替え
- **履歴** — 月次スナップショットの記録・折れ線グラフで推移確認
- **データ永続化** — localStorage(サーバー不要、ブラウザ内に保存)
- **レスポンシブ対応** — デスクトップはサイドバー、スマホはボトムナビ

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
## 資産カテゴリ

## Expanding the ESLint configuration
| カテゴリ | 分類 | 説明 |
|----------|------|------|
| 現金・預金 | — | 銀行口座、ゆうちょなど |
| 株式 | 伝統的資産 | 国内株・外国株・ETFなど |
| 投資信託・ロボアド | 伝統的資産 | インデックスファンド、ロボアドバイザーなど |
| 年金・確定拠出年金(iDeCo) | 伝統的資産 | 企業型DC・iDeCoなど |
| **暗号資産** | オルタナティブ | BTC・ETHなど(数量×単価で円換算) |
| 金・貴金属 | オルタナティブ | 金・プラチナなど |
| その他 | — | 上記に当てはまらないもの |

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
## 技術スタック

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
- [React](https://react.dev/) + [Vite](https://vite.dev/) + TypeScript
- [Tailwind CSS v3](https://tailwindcss.com/)
- [Recharts](https://recharts.org/)(グラフ)
- [React Router](https://reactrouter.com/)

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,
## ローカル開発

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```bash
git clone https://github.com/ShibaInuChan/Asset-Management.git
cd Asset-Management
npm install
npm run dev
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
ブラウザで http://localhost:5173 を開く。

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
## デプロイ

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
mainブランチへのプッシュで Vercel が自動デプロイします。
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vite-template</title>
<title>資産管理ダッシュボード</title>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function Layout({ children }: { children: ReactNode }) {

{/* Main content */}
<main className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 overflow-y-auto pb-20 md:pb-0">
<div className="flex-1 overflow-y-auto pb-32 md:pb-0">
{children}
</div>
</main>
Expand Down
32 changes: 22 additions & 10 deletions src/data/categories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,27 @@ export interface Category {
}

export const CATEGORIES: Category[] = [
{ key: 'cash', label: '現金・預金', color: '#3B82F6', bgColor: 'bg-blue-100', textColor: 'text-blue-700' },
{ key: 'jp_stock', label: '国内株式', color: '#10B981', bgColor: 'bg-emerald-100', textColor: 'text-emerald-700' },
{ key: 'foreign_stock', label: '外国株式', color: '#6366F1', bgColor: 'bg-indigo-100', textColor: 'text-indigo-700' },
{ key: 'fund', label: '投資信託', color: '#F59E0B', bgColor: 'bg-amber-100', textColor: 'text-amber-700' },
{ key: 'crypto', label: '暗号資産', color: '#F97316', bgColor: 'bg-orange-100', textColor: 'text-orange-700' },
{ key: 'real_estate', label: '不動産', color: '#8B5CF6', bgColor: 'bg-violet-100', textColor: 'text-violet-700' },
{ key: 'insurance', label: '保険', color: '#EC4899', bgColor: 'bg-pink-100', textColor: 'text-pink-700' },
{ key: 'other', label: 'その他', color: '#6B7280', bgColor: 'bg-gray-100', textColor: 'text-gray-700' },
{ key: 'cash', label: '現金・預金', color: '#2979FF', bgColor: 'bg-blue-100', textColor: 'text-blue-700' },
{ key: 'stock', label: '株式', color: '#E53935', bgColor: 'bg-red-100', textColor: 'text-red-700' },
{ key: 'fund', label: '投資信託・ロボアド', color: '#FFB300', bgColor: 'bg-amber-100', textColor: 'text-amber-700' },
{ key: 'crypto', label: '暗号資産', color: '#8E24AA', bgColor: 'bg-purple-100', textColor: 'text-purple-700' },
{ key: 'gold', label: '金・貴金属', color: '#FF6D00', bgColor: 'bg-orange-100', textColor: 'text-orange-700' },
{ key: 'pension', label: '年金・確定拠出年金(iDeCo)', color: '#00ACC1', bgColor: 'bg-cyan-100', textColor: 'text-cyan-700' },
{ key: 'other', label: 'その他', color: '#9E9E9E', bgColor: 'bg-gray-100', textColor: 'text-gray-600' },
];

export const getCategoryByKey = (key: string): Category =>
CATEGORIES.find(c => c.key === key) ?? CATEGORIES[CATEGORIES.length - 1];
// 旧キーを新キーにマッピング(localStorage の既存データ互換)
const KEY_ALIASES: Record<string, string> = {
jp_stock: 'stock',
foreign_stock: 'stock',
robo_advisor: 'fund',
real_estate: 'other',
insurance: 'other',
};

export const normalizeKey = (key: string): string => KEY_ALIASES[key] ?? key;

export const getCategoryByKey = (key: string): Category => {
const normalized = normalizeKey(key);
return CATEGORIES.find(c => c.key === normalized) ?? CATEGORIES[CATEGORIES.length - 1];
};
6 changes: 2 additions & 4 deletions src/data/sampleData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ const now = new Date().toISOString();
export const sampleAssets: Asset[] = [
{ id: '1', name: '普通預金 三菱UFJ', category: 'cash', amount: 500000, memo: '', updatedAt: now },
{ id: '2', name: '定期預金 ゆうちょ', category: 'cash', amount: 1000000, memo: '', updatedAt: now },
{ id: '3', name: 'トヨタ株', category: 'jp_stock', amount: 300000, memo: '', updatedAt: now },
{ id: '4', name: 'S&P500 ETF', category: 'foreign_stock', amount: 450000, memo: '', updatedAt: now },
{ id: '3', name: 'トヨタ株', category: 'stock', amount: 300000, memo: '', updatedAt: now },
{ id: '4', name: 'S&P500 ETF', category: 'stock', amount: 450000, memo: '', updatedAt: now },
{ id: '5', name: 'eMAXIS Slim 全世界株', category: 'fund', amount: 200000, memo: '', updatedAt: now },
{ id: '6', name: 'Bitcoin', category: 'crypto', amount: 2100000, quantity: 0.15, unitPrice: 14000000, memo: 'BTC', updatedAt: now },
{ id: '7', name: 'Ethereum', category: 'crypto', amount: 1000000, quantity: 2, unitPrice: 500000, memo: 'ETH', updatedAt: now },
{ id: '8', name: 'マンション評価額', category: 'real_estate', amount: 25000000, memo: '', updatedAt: now },
{ id: '9', name: '生命保険 解約返戻金', category: 'insurance', amount: 800000, memo: '', updatedAt: now },
];

const totalNow = sampleAssets.reduce((s, a) => s + a.amount, 0);
Expand Down
28 changes: 25 additions & 3 deletions src/hooks/useAssets.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
import { useState, useEffect } from 'react';
import { type Asset } from '../types';
import { sampleAssets } from '../data/sampleData';
import { normalizeKey } from '../data/categories';

const KEY = 'assets';

function migrate(assets: Asset[]): Asset[] {
return assets.map(a => ({ ...a, category: normalizeKey(a.category) }));
}

function load(): Asset[] {
try {
const raw = localStorage.getItem(KEY);
if (raw) return JSON.parse(raw) as Asset[];
if (raw) {
const parsed = JSON.parse(raw) as Asset[];
const migrated = migrate(parsed);
// 書き戻して次回以降は正規化済みで読める
localStorage.setItem(KEY, JSON.stringify(migrated));
return migrated;
}
} catch {
// ignore
}
// First time: load sample data
localStorage.setItem(KEY, JSON.stringify(sampleAssets));
return sampleAssets;
}
Expand Down Expand Up @@ -49,5 +59,17 @@ export function useAssets() {
setAssets(prev => prev.filter(a => a.id !== id));
}

return { assets, addAsset, updateAsset, deleteAsset };
function reorderAssets(fromId: string, toId: string) {
setAssets(prev => {
const list = [...prev];
const from = list.findIndex(a => a.id === fromId);
const to = list.findIndex(a => a.id === toId);
if (from === -1 || to === -1 || from === to) return prev;
const [item] = list.splice(from, 1);
list.splice(to, 0, item);
return list;
});
}

return { assets, addAsset, updateAsset, deleteAsset, reorderAssets };
}
17 changes: 16 additions & 1 deletion src/hooks/useSnapshots.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
import { useState, useEffect } from 'react';
import { type Snapshot, type Asset } from '../types';
import { sampleSnapshots } from '../data/sampleData';
import { normalizeKey } from '../data/categories';

const KEY = 'snapshots';

function migrateSnapshot(snap: Snapshot): Snapshot {
const byCategory: Record<string, number> = {};
for (const [k, v] of Object.entries(snap.byCategory)) {
const key = normalizeKey(k);
byCategory[key] = (byCategory[key] ?? 0) + v;
}
return { ...snap, byCategory };
}

function load(): Snapshot[] {
try {
const raw = localStorage.getItem(KEY);
if (raw) return JSON.parse(raw) as Snapshot[];
if (raw) {
const parsed = JSON.parse(raw) as Snapshot[];
const migrated = parsed.map(migrateSnapshot);
localStorage.setItem(KEY, JSON.stringify(migrated));
return migrated;
}
} catch {
// ignore
}
Expand Down
8 changes: 8 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ body {
#root {
min-height: 100vh;
}

/* Remove focus outline on recharts SVG elements */
.recharts-sector:focus,
.recharts-surface:focus,
.recharts-wrapper svg:focus,
.recharts-pie-sector path:focus {
outline: none;
}
Loading