Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"url": "http://localhost:3000",
"port": 3000
},
{
"name": "warikapp-dev-server",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 3000
},
{
"name": "warikapp-prod",
"runtimeExecutable": "npm",
Expand Down
14 changes: 7 additions & 7 deletions app/(app)/expenses/[id]/edit/expense-edit-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,26 @@ export default function ExpenseEditClient({
}

if (isLoading) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (!isAuthenticated) {
return null; // 未ログイン: proxyが/loginへ誘導する
}
if (member === undefined) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (member === null) {
return null; // 世帯未所属: /setupへ誘導中
}
if (expense === undefined || household === undefined) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (expense === null) {
// 他世帯・削除済み・存在しないIDはすべて同じ表示にする(存在を漏らさない)
return (
<main className="mx-auto w-full max-w-md space-y-4 p-6">
<p className="text-sm">支出が見つかりません</p>
<Link href="/" className="block text-sm text-blue-600 underline">
<Link href="/" className="block text-sm font-medium text-me-strong underline underline-offset-4">
ホームへ戻る
</Link>
</main>
Expand All @@ -98,7 +98,7 @@ export default function ExpenseEditClient({
<p className="text-sm">精算済みの記録は変更できません</p>
<Link
href={`/expenses/${expense._id}`}
className="block text-sm text-blue-600 underline"
className="block text-sm font-medium text-me-strong underline underline-offset-4"
>
支出の詳細へ戻る
</Link>
Expand All @@ -111,15 +111,15 @@ export default function ExpenseEditClient({
<div>
<Link
href={`/expenses/${expense._id}`}
className="text-sm text-blue-600 underline"
className="text-sm font-medium text-me-strong underline underline-offset-4"
>
← 詳細に戻る
</Link>
<h1 className="mt-2 text-xl font-bold">
{expense.status === "draft" ? "下書きを確認" : "支出を編集"}
</h1>
{expense.status === "draft" && (
<p className="mt-1 text-sm text-gray-500">
<p className="mt-1 text-sm text-muted">
確定するとホームの未精算差額に反映されます。
</p>
)}
Expand Down
69 changes: 35 additions & 34 deletions app/(app)/expenses/[id]/expense-detail-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Id } from "@/convex/_generated/dataModel";
import { toUserMessage } from "@/lib/convex-error";
import { formatDateLabel, formatYen } from "@/lib/format";
import { calcAdvanceAmount, calcItemShareAmount } from "@/lib/settlement";
import { memberColorClass } from "@/lib/ui";
import { useConvexAuth, useMutation, useQuery } from "convex/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
Expand All @@ -15,10 +16,10 @@ import { useEffect, useState } from "react";
// 精算済みの支出は閲覧のみ(サーバー側でも expenses.save / remove が拒否する)。

const badgeClass =
"rounded-full px-2 py-0.5 text-xs font-medium whitespace-nowrap";
"rounded-full px-2 py-0.5 text-xs font-bold whitespace-nowrap";

const buttonClass =
"flex-1 rounded-md border border-black/15 px-4 py-3 text-center text-sm font-medium disabled:opacity-50 dark:border-white/25";
"flex-1 rounded-full border border-edge bg-surface px-4 py-3 text-center text-sm font-medium disabled:opacity-50";

export default function ExpenseDetailClient({
expenseId,
Expand Down Expand Up @@ -71,28 +72,28 @@ export default function ExpenseDetailClient({
}

if (isLoading) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (!isAuthenticated) {
return null; // 未ログイン: proxyが/loginへ誘導する
}
if (member === undefined) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (member === null) {
return null; // 世帯未所属: /setupへ誘導中
}
// 支払者名と立て替え額の相手を household から引くため、揃うまで待つ
// (先に出すと名前が空欄になり、立て替え額も「なし」と誤って表示される)
if (expense === undefined || household === undefined) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (expense === null) {
// 他世帯・削除済み・存在しないIDはすべて同じ表示にする(存在を漏らさない)
return (
<main className="mx-auto w-full max-w-md space-y-4 p-6">
<p className="text-sm">支出が見つかりません</p>
<Link href="/" className="block text-sm text-blue-600 underline">
<Link href="/" className="block text-sm font-medium text-me-strong underline underline-offset-4">
ホームへ戻る
</Link>
</main>
Expand Down Expand Up @@ -120,31 +121,27 @@ export default function ExpenseDetailClient({
return (
<main className="mx-auto w-full max-w-md space-y-6 p-6">
<div>
<Link href="/" className="text-sm text-blue-600 underline">
<Link href="/" className="text-sm font-medium text-me-strong underline underline-offset-4">
← ホーム
</Link>
<h1 className="mt-2 text-xl font-bold">
{expense.storeName ?? expense.items[0]?.name ?? "(名称なし)"}
</h1>
<div className="mt-2 flex flex-wrap items-center gap-2">
{expense.status === "draft" && (
<span
className={`${badgeClass} bg-amber-100 text-amber-900 dark:bg-amber-900 dark:text-amber-100`}
>
<span className={`${badgeClass} bg-warn-soft text-warn-strong`}>
未確定
</span>
)}
{expense.settled && (
<span
className={`${badgeClass} bg-black/10 text-gray-600 dark:bg-white/15 dark:text-gray-300`}
>
<span className={`${badgeClass} bg-line text-muted`}>
精算済み
</span>
)}
<span className="text-sm text-gray-500">
<span className="text-sm text-muted">
{formatDateLabel(expense.purchasedAt)}
</span>
<span className="text-sm text-gray-500">
<span className="text-sm text-muted">
{payerName}が支払い
</span>
</div>
Expand All @@ -154,10 +151,10 @@ export default function ExpenseDetailClient({
<section className="space-y-1">
<h2 className="text-sm font-semibold">レシート画像</h2>
{imageUrl === undefined ? (
<p className="text-sm text-gray-500">読み込み中…</p>
<p className="text-sm text-muted">読み込み中…</p>
) : imageUrl === null ? (
// 読み込み中(undefined)と区別する。null は保存先から画像が消えた場合
<p className="text-sm text-gray-500">画像を表示できませんでした</p>
<p className="text-sm text-muted">画像を表示できませんでした</p>
) : (
// タップで拡大(新しいタブで原寸を開く)。Convexの署名付きURLなので
// next/image のリモートホスト設定を増やさず素の img を使う
Expand All @@ -166,7 +163,7 @@ export default function ExpenseDetailClient({
<img
src={imageUrl}
alt="レシート"
className="max-h-48 rounded-lg border border-black/15 object-contain dark:border-white/25"
className="max-h-48 rounded-2xl border border-line object-contain"
/>
</a>
)}
Expand All @@ -177,27 +174,31 @@ export default function ExpenseDetailClient({
<h2 className="text-sm font-semibold">品目と仕分け</h2>
<ul className="space-y-2">
{expense.items.map((item, index) => (
<li
key={index}
className="rounded-lg border border-black/15 p-3 dark:border-white/25"
>
<li key={index} className="rounded-2xl bg-surface p-3 shadow-card">
<div className="flex items-baseline justify-between gap-3">
<span className="min-w-0 truncate font-medium">
<span className="min-w-0 truncate font-bold">
{item.name}
{item.quantity > 1 && (
<span className="text-sm text-gray-500">
<span className="text-sm font-medium text-muted">
{" "}
× {item.quantity}
</span>
)}
</span>
<span className="whitespace-nowrap font-bold">
<span className="whitespace-nowrap font-bold tabular-nums">
{formatYen(item.price * item.quantity)}
</span>
</div>
<ul className="mt-1 space-y-0.5 text-xs text-gray-500">
<ul className="mt-1 space-y-0.5 text-xs text-muted">
{item.shares.map((share) => (
<li key={share.memberId}>
<li key={share.memberId} className="flex items-center gap-1.5">
{/* メンバー識別色(自分=青緑/相手=菫)。ホームの天秤バーと同じ */}
<span
aria-hidden
className={`size-2 rounded-full ${memberColorClass(
share.memberId === household.self._id,
)}`}
/>
{memberName(share.memberId)} {share.ratioPercent}% ・{" "}
{formatYen(calcItemShareAmount(item, share.memberId))}
</li>
Expand All @@ -208,15 +209,15 @@ export default function ExpenseDetailClient({
</ul>
</section>

<section className="space-y-2 rounded-lg border border-black/15 p-4 dark:border-white/25">
<section className="space-y-2 rounded-2xl bg-surface p-4 shadow-card">
<div className="flex items-baseline justify-between">
<span className="text-sm text-gray-500">合計</span>
<span className="text-lg font-bold">
<span className="text-sm text-muted">合計</span>
<span className="text-lg font-bold tabular-nums">
{formatYen(expense.totalAmount)}
</span>
</div>
<div className="flex items-baseline justify-between">
<span className="text-sm text-gray-500">立て替え額</span>
<span className="text-sm text-muted">立て替え額</span>
<span className="text-sm">
{advanceAmount === 0 || otherId === null
? "なし"
Expand All @@ -226,12 +227,12 @@ export default function ExpenseDetailClient({
</section>

{expense.settled && (
<p className="text-sm text-gray-500">
<p className="text-sm text-muted">
精算済みの記録は変更できません
</p>
)}
{error !== null && (
<p role="alert" className="text-sm text-red-600">
<p role="alert" className="text-sm text-danger">
{error}
</p>
)}
Expand All @@ -250,7 +251,7 @@ export default function ExpenseDetailClient({
type="button"
onClick={handleRemove}
disabled={expense.settled || removing}
className={`${buttonClass} text-red-600`}
className={`${buttonClass} text-danger`}
>
{removing ? "削除中…" : "削除"}
</button>
Expand Down
8 changes: 4 additions & 4 deletions app/(app)/expenses/new/manual/manual-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ export default function ManualExpenseClient() {
// 未認証の判定を query の読み込み判定より先に行う(未認証では query が "skip" で
// undefined のまま止まるため、順序を逆にすると「読み込み中…」から抜けられない)
if (isLoading) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (!isAuthenticated) {
return null; // 未ログイン: proxyが/loginへ誘導する
}
if (member === undefined || (member !== null && household === undefined)) {
return <main className="p-8 text-gray-500">読み込み中…</main>;
return <main className="p-8 text-muted">読み込み中…</main>;
}
if (member === null || household === undefined) {
return null; // 世帯未所属: /setupへ誘導中
Expand All @@ -71,7 +71,7 @@ export default function ManualExpenseClient() {
<main className="mx-auto w-full max-w-md space-y-5 p-6">
<div>
<h1 className="text-xl font-bold">支出を手入力</h1>
<p className="mt-1 text-sm text-gray-500">
<p className="mt-1 text-sm text-muted">
品目名と金額だけで登録できます。負担区分は初期値が「折半」です。
</p>
</div>
Expand All @@ -95,7 +95,7 @@ export default function ManualExpenseClient() {
onSubmit={handleSubmit}
/>

<Link href="/" className="block text-sm text-blue-600 underline">
<Link href="/" className="block text-sm font-medium text-me-strong underline underline-offset-4">
ホームへ戻る
</Link>
</main>
Expand Down
Loading
Loading