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
16 changes: 8 additions & 8 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@

## マイルストーン(週次)

| 期間 | マイルストーン | 主な作業 | 対応ストーリー |
| ---------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| Sprint 0(設計) | 計画が固まる | 要件概要・ユーザーストーリー・データモデル・ロードマップ・ADR を Markdown で整備(=本ドキュメント群) | — |
| 準備(〜2 日) | 土台が立つ | `create-next-app`(TS)/ESLint・Prettier・tsconfig strict/`.env.example` 作成/GitHub Issues・Projects 起票/Vercel・Neon 接続/Prisma 初期化 | — |
| 第 1 週 | 認証と DB の土台 | Prisma スキーマ(User・Bottle)+マイグレーション/Better Auth で Google ログイン・ログアウト・保護ルート(セッション戦略・保護方式は現行の公式推奨を確認して決定)/空のボトル一覧ページ(プレースホルダ=ログイン後の着地点)/「自分のデータだけ」の基盤 | US-1 |
| 第 2 週 | ボトル CRUD(芯の本体) | 登録フォーム(zod・必須は銘柄名)/一覧(モバイルファースト・最小フィルタ・空状態)/詳細/編集/削除(確認・所有権)/Route Handlers(書き込み)+認可・読みは Server Component 直読み/**重要ロジック(zod・認可)の単体・結合テストを実装と併走**(→ `CONTRIBUTING.md` テスト方針) | US-2〜US-6 |
| 第 3 週 | 可視化 + デプロイ | ダッシュボード(国別の本数グラフ)/モバイル UI 調整/本番デプロイ(Vercel)・動作確認/README を実態に合わせて更新(デプロイ URL 反映) | US-7 |
| 第 4 週 | 品質 + 仕上げ + 父 FB | **E2E(Playwright・主要フロー 1 本)+ テストを細部まで拡充**+ GitHub Actions(CI)※導入時に CLAUDE.md の Commands と PR テンプレへ test を追記/バグ修正・UI 磨き/README 充実(スクショ・デモ GIF)・アーキ図/**父に実際に使ってもらいフィードバック収集**/予備日 | 共通 DoD |
| 期間 | マイルストーン | 主な作業 | 対応ストーリー |
| ---------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- |
| Sprint 0(設計) | 計画が固まる | 要件概要・ユーザーストーリー・データモデル・ロードマップ・ADR を Markdown で整備(=本ドキュメント群) | — |
| 準備(〜2 日) | 土台が立つ | `create-next-app`(TS)/ESLint・Prettier・tsconfig strict/`.env.example` 作成/GitHub Issues・Projects 起票/Vercel・Neon 接続/Prisma 初期化 | — |
| 第 1 週 | 認証と DB の土台 | Prisma スキーマ(User・Bottle)+マイグレーション/Better Auth で Google ログイン・ログアウト・保護ルート(セッション戦略・保護方式は現行の公式推奨を確認して決定)/空のボトル一覧ページ(プレースホルダ=ログイン後の着地点)/「自分のデータだけ」の基盤 | US-1 |
| 第 2 週 | ボトル CRUD(芯の本体) | 登録フォーム(zod・必須は銘柄名)/一覧(モバイルファースト・空状態)/詳細/編集/削除(確認・所有権)/Route Handlers(書き込み)+認可・読みは Server Component 直読み/**重要ロジック(zod・認可)の単体・結合テストを実装と併走**(→ `CONTRIBUTING.md` テスト方針) | US-2〜US-6 |
| 第 3 週 | 可視化 + デプロイ | ダッシュボード(国別の本数グラフ)/モバイル UI 調整/本番デプロイ(Vercel)・動作確認/README を実態に合わせて更新(デプロイ URL 反映) | US-7 |
| 第 4 週 | 品質 + 仕上げ + 父 FB | **E2E(Playwright・主要フロー 1 本)+ テストを細部まで拡充**+ GitHub Actions(CI)※導入時に CLAUDE.md の Commands と PR テンプレへ test を追記/バグ修正・UI 磨き/README 充実(スクショ・デモ GIF)・アーキ図/**父に実際に使ってもらいフィードバック収集**/予備日 | 共通 DoD |

> **MVP 完了の定義**:第 3 週末に「Google ログイン → ボトルを登録・一覧・編集・削除 → 傾向グラフ」が本番 URL で一通り動く。

Expand Down
4 changes: 0 additions & 4 deletions docs/user-stories.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,8 @@

- [ ] 自分のボトルが一覧で表示される(銘柄名・国・地域・年数・本数・限定版バッジ)
- [ ] スマホ画面で崩れず見やすい(モバイルファースト)
- [ ] 国(region)で絞り込みできる(最小 1 軸)
- [ ] 並べ替えができる(例:名前順/登録順、最小 1 つ)
- [ ] 0 件のとき「まだ登録がありません」などの空状態が表示される

> 検索・複合フィルタの強化は次点。MVP は最小の絞り込みでよい。

## US-4 ボトルの詳細を見る

**父として、1 本の詳細を見たい。記録した情報を確認するため。**
Expand Down
38 changes: 32 additions & 6 deletions src/app/bottles/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from "next/link";
import { redirect } from "next/navigation";
import { getSession } from "@/lib/session";
import { prisma } from "@/lib/prisma";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { LogoutButton } from "./logout-button";

Expand Down Expand Up @@ -30,13 +31,38 @@ export default async function BottlesPage() {
まだ登録がありません
</p>
) : (
// 登録反映の確認用の最小表示(一覧の本実装は #26)。
<ul className="flex flex-col gap-2">
{bottles.map((bottle) => (
<li key={bottle.id} className="rounded-md border px-4 py-3">
{bottle.name}
</li>
))}
{bottles.map((bottle) => {
// 題名は業界の呼称に合わせて「名称 年数」(NAS は名称のみ)。
const title = bottle.age
? `${bottle.name} ${bottle.age}年`
: bottle.name;
// 国・地域は中黒でつなぐテキスト(未入力は filter で落とす)。
const meta = [bottle.region, bottle.subRegion].filter(Boolean);
return (
<li
key={bottle.id}
className="flex flex-col gap-1 rounded-md border px-4 py-3"
>
<div className="flex items-center justify-between gap-2">
<span className="min-w-0 font-medium break-words">
{title}
</span>
<span className="shrink-0 text-sm text-muted-foreground">
{bottle.quantity}本
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</span>
</div>
{(meta.length > 0 || bottle.isLimited) && (
<div className="flex items-center gap-2 text-sm text-muted-foreground">
{meta.length > 0 && <span>{meta.join("・")}</span>}
{bottle.isLimited && (
<Badge variant="secondary">限定版</Badge>
)}
</div>
)}
</li>
);
})}
</ul>
)}
</main>
Expand Down
49 changes: 49 additions & 0 deletions src/components/ui/badge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { Slot } from "radix-ui";

import { cn } from "@/lib/utils";

const badgeVariants = cva(
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
secondary:
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
destructive:
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
outline:
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
ghost:
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
link: "text-primary underline-offset-4 hover:underline",
},
},
defaultVariants: {
variant: "default",
},
},
);

function Badge({
className,
variant = "default",
asChild = false,
...props
}: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "span";

return (
<Comp
data-slot="badge"
data-variant={variant}
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}

export { Badge, badgeVariants };
Loading