From b2d563d8702b575d8eb52e282f009221d298e1fb Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 00:26:20 +0000 Subject: [PATCH 01/28] Add .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bef997 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +dist-ssr/ +*.local +.env +.DS_Store From 9ccc291631e71e1a1ba5aa8b92739926e82d95c7 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 00:50:12 +0000 Subject: [PATCH 02/28] Add vercel.json for SPA routing --- vercel.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 vercel.json diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..0f32683 --- /dev/null +++ b/vercel.json @@ -0,0 +1,3 @@ +{ + "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] +} From b25f002ca916faaa1ed277c974e286ce7f019553 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 00:54:56 +0000 Subject: [PATCH 03/28] Fix dashboard: title, chart height, category percentages --- index.html | 2 +- src/pages/Dashboard.tsx | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/index.html b/index.html index 7fe0608..782c97b 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - vite-template + 資産管理
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 247a987..c71129e 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -55,14 +55,14 @@ export function Dashboard() {

カテゴリ別内訳

{chartData.length > 0 ? ( - + @@ -75,6 +75,7 @@ export function Dashboard() { contentStyle={{ fontSize: 12 }} /> {value}} /> @@ -89,17 +90,21 @@ export function Dashboard() { {/* Category summary */}

カテゴリ別合計

-
+
{CATEGORIES.filter(c => (byCategory[c.key] ?? 0) > 0).map(cat => { const amt = byCategory[cat.key] ?? 0; - const pct = total > 0 ? Math.round((amt / total) * 100) : 0; + const pct = total > 0 ? (amt / total) * 100 : 0; + const pctDisplay = total > 0 ? pct.toFixed(1) : '0.0'; return (
-
+
{cat.label} - {formatJPY(amt)} +
+ {pctDisplay}% + {formatJPY(amt)} +
-
+
Date: Wed, 3 Jun 2026 00:56:56 +0000 Subject: [PATCH 04/28] Fix pie chart tooltip: show category name and percentage --- src/pages/Dashboard.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index c71129e..4a05994 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -71,7 +71,11 @@ export function Dashboard() { ))} [formatJPY(Number(value)), '']} + formatter={(value, name) => { + const amt = Number(value); + const pct = total > 0 ? ((amt / total) * 100).toFixed(1) : '0.0'; + return [`${formatJPY(amt)}(${pct}%)`, name]; + }} contentStyle={{ fontSize: 12 }} /> Date: Wed, 3 Jun 2026 00:59:36 +0000 Subject: [PATCH 05/28] Update README --- README.md | 93 ++++++++++++++++++++----------------------------------- 1 file changed, 34 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 7dbf7eb..7bee66d 100644 --- a/README.md +++ b/README.md @@ -1,73 +1,48 @@ -# 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など | +| 投資信託 | インデックスファンドなど | +| **暗号資産** | 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 が自動デプロイします。 From a84c21c6cc1c6f2bf6b9e8ce758552ac39671d41 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:13:01 +0000 Subject: [PATCH 06/28] Add pension/iDeCo category --- src/data/categories.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/data/categories.ts b/src/data/categories.ts index fd5aee8..3f7a4b7 100644 --- a/src/data/categories.ts +++ b/src/data/categories.ts @@ -14,6 +14,7 @@ export const CATEGORIES: Category[] = [ { 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: 'pension', label: '年金・iDeCo', color: '#14B8A6', bgColor: 'bg-teal-100', textColor: 'text-teal-700' }, { key: 'other', label: 'その他', color: '#6B7280', bgColor: 'bg-gray-100', textColor: 'text-gray-700' }, ]; From 525075d804265655455263cad8b53a0015804451 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:15:54 +0000 Subject: [PATCH 07/28] Add pension category, fix formatJPY to show exact amounts --- src/data/categories.ts | 2 +- src/pages/Assets.tsx | 7 +------ src/pages/Dashboard.tsx | 7 +------ src/pages/History.tsx | 7 +------ src/utils/format.ts | 18 ++++++++++++++++++ 5 files changed, 22 insertions(+), 19 deletions(-) create mode 100644 src/utils/format.ts diff --git a/src/data/categories.ts b/src/data/categories.ts index 3f7a4b7..ac677ff 100644 --- a/src/data/categories.ts +++ b/src/data/categories.ts @@ -14,7 +14,7 @@ export const CATEGORIES: Category[] = [ { 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: 'pension', label: '年金・iDeCo', color: '#14B8A6', bgColor: 'bg-teal-100', textColor: 'text-teal-700' }, + { key: 'pension', label: '年金・確定拠出年金(iDeCo)', color: '#14B8A6', bgColor: 'bg-teal-100', textColor: 'text-teal-700' }, { key: 'other', label: 'その他', color: '#6B7280', bgColor: 'bg-gray-100', textColor: 'text-gray-700' }, ]; diff --git a/src/pages/Assets.tsx b/src/pages/Assets.tsx index 04097fa..d3fc515 100644 --- a/src/pages/Assets.tsx +++ b/src/pages/Assets.tsx @@ -2,12 +2,7 @@ import { useState } from 'react'; import { useAssets } from '../hooks/useAssets'; import { CATEGORIES } from '../data/categories'; import type { Asset } from '../types'; - -function formatJPY(amount: number): string { - if (amount >= 100000000) return `${(amount / 100000000).toFixed(2)}億円`; - if (amount >= 10000) return `${Math.floor(amount / 10000).toLocaleString()}万円`; - return `${amount.toLocaleString()}円`; -} +import { formatJPY } from '../utils/format'; const EMPTY_FORM = { name: '', diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 4a05994..49aa303 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,12 +1,7 @@ import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { useAssets } from '../hooks/useAssets'; import { CATEGORIES, getCategoryByKey } from '../data/categories'; - -function formatJPY(amount: number): string { - if (amount >= 100000000) return `${(amount / 100000000).toFixed(2)}億円`; - if (amount >= 10000) return `${Math.floor(amount / 10000).toLocaleString()}万円`; - return `${amount.toLocaleString()}円`; -} +import { formatJPY } from '../utils/format'; function formatDate(iso: string): string { const d = new Date(iso); diff --git a/src/pages/History.tsx b/src/pages/History.tsx index 6331639..c4af1da 100644 --- a/src/pages/History.tsx +++ b/src/pages/History.tsx @@ -2,12 +2,7 @@ import { useAssets } from '../hooks/useAssets'; import { useSnapshots } from '../hooks/useSnapshots'; import { CATEGORIES } from '../data/categories'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; - -function formatJPY(amount: number): string { - if (amount >= 100000000) return `${(amount / 100000000).toFixed(2)}億円`; - if (amount >= 10000) return `${Math.floor(amount / 10000).toLocaleString()}万円`; - return `${amount.toLocaleString()}円`; -} +import { formatJPY } from '../utils/format'; export function History() { const { assets } = useAssets(); diff --git a/src/utils/format.ts b/src/utils/format.ts new file mode 100644 index 0000000..c3f312e --- /dev/null +++ b/src/utils/format.ts @@ -0,0 +1,18 @@ +export function formatJPY(amount: number): string { + if (amount >= 100000000) { + const oku = Math.floor(amount / 100000000); + const remainder = amount % 100000000; + if (remainder === 0) return `${oku.toLocaleString()}億円`; + const man = Math.floor(remainder / 10000); + const yen = remainder % 10000; + if (yen === 0) return `${oku.toLocaleString()}億${man.toLocaleString()}万円`; + return `${oku.toLocaleString()}億${man.toLocaleString()}万${yen.toLocaleString()}円`; + } + if (amount >= 10000) { + const man = Math.floor(amount / 10000); + const yen = amount % 10000; + if (yen === 0) return `${man.toLocaleString()}万円`; + return `${man.toLocaleString()}万${yen.toLocaleString()}円`; + } + return `${amount.toLocaleString()}円`; +} From eed365f38ab1d0396e2f8342513598e4fa59df05 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:19:36 +0000 Subject: [PATCH 08/28] =?UTF-8?q?Unify=20number=20format:=20short=20(150.9?= =?UTF-8?q?=E4=B8=87)=20for=20summaries,=20full=20(1,509,300=E5=86=86)=20f?= =?UTF-8?q?or=20individual=20assets?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Dashboard.tsx | 10 +++++----- src/pages/History.tsx | 14 +++++--------- src/utils/format.ts | 23 ++++++++--------------- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 49aa303..c6c2acf 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,7 +1,7 @@ import { PieChart, Pie, Cell, Tooltip, ResponsiveContainer, Legend } from 'recharts'; import { useAssets } from '../hooks/useAssets'; import { CATEGORIES, getCategoryByKey } from '../data/categories'; -import { formatJPY } from '../utils/format'; +import { formatJPY, formatJPYShort } from '../utils/format'; function formatDate(iso: string): string { const d = new Date(iso); @@ -40,8 +40,8 @@ export function Dashboard() { {/* Total net assets */}

総資産

-

{formatJPY(total)}

-

{total.toLocaleString()}円

+

{formatJPYShort(total)}

+

{formatJPY(total)}

{/* Chart + Category cards */} @@ -69,7 +69,7 @@ export function Dashboard() { formatter={(value, name) => { const amt = Number(value); const pct = total > 0 ? ((amt / total) * 100).toFixed(1) : '0.0'; - return [`${formatJPY(amt)}(${pct}%)`, name]; + return [`${formatJPYShort(amt)}(${pct}%)`, name]; }} contentStyle={{ fontSize: 12 }} /> @@ -100,7 +100,7 @@ export function Dashboard() { {cat.label}
{pctDisplay}% - {formatJPY(amt)} + {formatJPYShort(amt)}
diff --git a/src/pages/History.tsx b/src/pages/History.tsx index c4af1da..a26d118 100644 --- a/src/pages/History.tsx +++ b/src/pages/History.tsx @@ -2,7 +2,7 @@ import { useAssets } from '../hooks/useAssets'; import { useSnapshots } from '../hooks/useSnapshots'; import { CATEGORIES } from '../data/categories'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts'; -import { formatJPY } from '../utils/format'; +import { formatJPY, formatJPYShort } from '../utils/format'; export function History() { const { assets } = useAssets(); @@ -43,16 +43,12 @@ export function History() { { - if (v >= 100000000) return `${(v / 100000000).toFixed(1)}億`; - if (v >= 10000) return `${Math.round(v / 10000)}万`; - return String(v); - }} + tickFormatter={v => formatJPYShort(Number(v))} tick={{ fontSize: 11, fill: '#9CA3AF' }} width={60} /> [formatJPY(Number(value)), '総資産']} + formatter={(value) => [formatJPYShort(Number(value)), '総資産']} contentStyle={{ fontSize: 12, borderRadius: 8 }} />

{snap.date}

-

{formatJPY(snap.total)}

+

{formatJPYShort(snap.total)}

    {items.map(asset => ( -
  • +
  • handleDragStart(asset.id)} + onDragOver={e => handleDragOver(e, asset.id)} + onDrop={() => handleDrop(asset.id)} + onDragEnd={handleDragEnd} + className={`flex items-center justify-between px-5 py-4 transition-colors ${dragOverId === asset.id ? 'bg-blue-50' : ''}`} + > +

    {asset.name}

    {asset.quantity != null && asset.unitPrice != null && ( From 2e818aa2edfcc50bd95401422aeb6ccc799f8275 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:43:15 +0000 Subject: [PATCH 16/28] Migrate localStorage data: normalize legacy category keys on load --- src/hooks/useAssets.ts | 14 ++++++++++++-- src/hooks/useSnapshots.ts | 17 ++++++++++++++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/hooks/useAssets.ts b/src/hooks/useAssets.ts index 4d89091..5421b31 100644 --- a/src/hooks/useAssets.ts +++ b/src/hooks/useAssets.ts @@ -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; } diff --git a/src/hooks/useSnapshots.ts b/src/hooks/useSnapshots.ts index eeabeb3..c64f77e 100644 --- a/src/hooks/useSnapshots.ts +++ b/src/hooks/useSnapshots.ts @@ -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 = {}; + 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 } From 9d8d0c5e7f9255d52453b92606ae7963cd7b96dc Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:48:36 +0000 Subject: [PATCH 17/28] Add investment and alternative asset sub-totals to total card --- src/pages/Dashboard.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 9af3be0..5f59373 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -42,6 +42,20 @@ export function Dashboard() {

    総資産

    {formatJPY(total)}

    +
    +
    +

    運用資産

    +

    + {formatJPY(['stock', 'fund', 'pension'].reduce((s, k) => s + (byCategory[k] ?? 0), 0))} +

    +
    +
    +

    オルタナティブ資産

    +

    + {formatJPY(['crypto', 'gold'].reduce((s, k) => s + (byCategory[k] ?? 0), 0))} +

    +
    +
    {/* Chart + Category cards */} From c37a6deb3cf142d9ccff24820155e6d54811a412 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 01:55:44 +0000 Subject: [PATCH 18/28] Split total card into traditional, alternative, and total investment assets --- src/pages/Dashboard.tsx | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 5f59373..f47b601 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -43,18 +43,26 @@ export function Dashboard() {

    総資産

    {formatJPY(total)}

    -
    -

    運用資産

    -

    - {formatJPY(['stock', 'fund', 'pension'].reduce((s, k) => s + (byCategory[k] ?? 0), 0))} -

    -
    -
    -

    オルタナティブ資産

    -

    - {formatJPY(['crypto', 'gold'].reduce((s, k) => s + (byCategory[k] ?? 0), 0))} -

    -
    + {(() => { + const traditional = ['stock', 'fund', 'pension'].reduce((s, k) => s + (byCategory[k] ?? 0), 0); + const alternative = ['crypto', 'gold'].reduce((s, k) => s + (byCategory[k] ?? 0), 0); + return ( + <> +
    +

    伝統的資産

    +

    {formatJPY(traditional)}

    +
    +
    +

    オルタナティブ資産

    +

    {formatJPY(alternative)}

    +
    +
    +

    運用資産合計

    +

    {formatJPY(traditional + alternative)}

    +
    + + ); + })()}
    From 3bf5f8b0f9c18933b26f47c333b0cdfe0b5cfacc Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 02:02:02 +0000 Subject: [PATCH 19/28] Update README to reflect current categories and features --- README.md | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7bee66d..45cf37d 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,28 @@ # 資産管理アプリ -マネーフォワードライクなブラウザ資産管理アプリ。**暗号資産を現金・預金と分けて管理**できることが特徴です。 +マネーフォワードライクなブラウザ資産管理アプリ。**暗号資産・金などのオルタナティブ資産を伝統的資産と分けて管理**できることが特徴です。 🌐 **[asset-management-nu-seven.vercel.app](https://asset-management-nu-seven.vercel.app)** ## 主な機能 -- **ダッシュボード** — 総資産・カテゴリ別ドーナツグラフ・割合表示 -- **資産一覧** — カテゴリ別グループ表示・追加/編集/削除 +- **ダッシュボード** — 総資産・伝統的資産/オルタナティブ資産/運用資産合計の内訳・カテゴリ別ドーナツグラフ(割合表示) +- **資産一覧** — カテゴリ別グループ表示・追加/編集/削除・ドラッグ&ドロップで並び替え - **履歴** — 月次スナップショットの記録・折れ線グラフで推移確認 - **データ永続化** — localStorage(サーバー不要、ブラウザ内に保存) - **レスポンシブ対応** — デスクトップはサイドバー、スマホはボトムナビ ## 資産カテゴリ -| カテゴリ | 説明 | -|----------|------| -| 現金・預金 | 銀行口座、ゆうちょなど | -| 国内株式 | 日本株 | -| 外国株式 | 米国株・ETFなど | -| 投資信託 | インデックスファンドなど | -| **暗号資産** | BTC・ETHなど(数量×単価で円換算) | -| 不動産 | マンション・土地など | -| 保険 | 解約返戻金など | -| その他 | 上記に当てはまらないもの | +| カテゴリ | 分類 | 説明 | +|----------|------|------| +| 現金・預金 | — | 銀行口座、ゆうちょなど | +| 株式 | 伝統的資産 | 国内株・外国株・ETFなど | +| 投資信託・ロボアド | 伝統的資産 | インデックスファンド、ロボアドバイザーなど | +| 年金・確定拠出年金(iDeCo) | 伝統的資産 | 企業型DC・iDeCoなど | +| **暗号資産** | オルタナティブ | BTC・ETHなど(数量×単価で円換算) | +| 金・貴金属 | オルタナティブ | 金・プラチナなど | +| その他 | — | 上記に当てはまらないもの | ## 技術スタック From dbcb8bf9b5a58a97b59e7511ab88d6cdff8fc304 Mon Sep 17 00:00:00 2001 From: Asset Management Bot Date: Wed, 3 Jun 2026 02:19:35 +0000 Subject: [PATCH 20/28] Fix mobile: content padding, modal height, touch drag-and-drop --- src/components/Layout.tsx | 2 +- src/pages/Assets.tsx | 77 +++++++++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx index c9d8103..2a20eb7 100644 --- a/src/components/Layout.tsx +++ b/src/components/Layout.tsx @@ -48,7 +48,7 @@ export function Layout({ children }: { children: ReactNode }) { {/* Main content */}
    -
    +
    {children}
    diff --git a/src/pages/Assets.tsx b/src/pages/Assets.tsx index da423fe..756ad78 100644 --- a/src/pages/Assets.tsx +++ b/src/pages/Assets.tsx @@ -20,6 +20,9 @@ export function Assets() { const [form, setForm] = useState(EMPTY_FORM); const [dragOverId, setDragOverId] = useState(null); const dragId = useRef(null); + // touch drag state + const touchDragId = useRef(null); + const touchOverId = useRef(null); const isCrypto = form.category === 'crypto'; @@ -78,24 +81,51 @@ export function Assets() { if (confirm('この資産を削除しますか?')) deleteAsset(id); } - function handleDragStart(id: string) { - dragId.current = id; - } - - function handleDragOver(e: React.DragEvent, id: string) { - e.preventDefault(); - setDragOverId(id); - } - + // Mouse drag handlers + function handleDragStart(id: string) { dragId.current = id; } + function handleDragOver(e: React.DragEvent, id: string) { e.preventDefault(); setDragOverId(id); } function handleDrop(toId: string) { if (dragId.current) reorderAssets(dragId.current, toId); dragId.current = null; setDragOverId(null); } + function handleDragEnd() { dragId.current = null; setDragOverId(null); } - function handleDragEnd() { - dragId.current = null; - setDragOverId(null); + // Touch drag handlers + function handleTouchStart(e: React.TouchEvent, id: string) { + touchDragId.current = id; + (e.currentTarget as HTMLElement).style.opacity = '0.5'; + } + + function handleTouchMove(e: React.TouchEvent) { + e.preventDefault(); + const touch = e.touches[0]; + const el = document.elementFromPoint(touch.clientX, touch.clientY); + const li = el?.closest('[data-asset-id]') as HTMLElement | null; + const overId = li?.dataset.assetId ?? null; + if (overId !== touchOverId.current) { + if (touchOverId.current) { + const prev = document.querySelector(`[data-asset-id="${touchOverId.current}"]`) as HTMLElement | null; + if (prev) prev.style.background = ''; + } + if (overId && overId !== touchDragId.current) { + li!.style.background = '#EFF6FF'; + } + touchOverId.current = overId; + } + } + + function handleTouchEnd(e: React.TouchEvent) { + (e.currentTarget as HTMLElement).style.opacity = ''; + if (touchDragId.current && touchOverId.current && touchDragId.current !== touchOverId.current) { + reorderAssets(touchDragId.current, touchOverId.current); + } + if (touchOverId.current) { + const el = document.querySelector(`[data-asset-id="${touchOverId.current}"]`) as HTMLElement | null; + if (el) el.style.background = ''; + } + touchDragId.current = null; + touchOverId.current = null; } // Group assets by category @@ -140,6 +170,7 @@ export function Assets() { {items.map(asset => (
  • handleDragStart(asset.id)} onDragOver={e => handleDragOver(e, asset.id)} @@ -147,7 +178,12 @@ export function Assets() { onDragEnd={handleDragEnd} className={`flex items-center justify-between px-5 py-4 transition-colors ${dragOverId === asset.id ? 'bg-blue-50' : ''}`} > -
    +
    handleTouchStart(e as unknown as React.TouchEvent, asset.id)} + onTouchMove={e => handleTouchMove(e as unknown as React.TouchEvent)} + onTouchEnd={e => handleTouchEnd(e as unknown as React.TouchEvent)} + >⠿

    {asset.name}

    {asset.quantity != null && asset.unitPrice != null && ( @@ -159,14 +195,8 @@ export function Assets() {

    {formatJPY(asset.amount)}

    - - + +
  • ))} @@ -179,7 +209,8 @@ export function Assets() { {showModal && (
    setShowModal(false)} /> -
    +

    {editing ? '資産を編集' : '資産を追加'}

    @@ -259,7 +290,7 @@ export function Assets() { className="w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
    -
    +