From 9e324bbc465abbba64656b68d98db51fe7c727b5 Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Mon, 11 May 2026 20:00:59 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Companies=20=E3=83=9A=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=92=20Fjord=20direction=20=E3=83=87=E3=82=B6=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=A7=E5=86=8D=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Design から handoff された Fjord direction (深いティール + Zen Maru Gothic + M PLUS Rounded 1c の和文丸ゴシック基調) を Companies ページに 適用する。 - 視覚言語は当該ページ内に閉じる: - 全スタイルを CSS Modules (Companies.module.css) でスコープ化 - フォントは next/font/google を companies/layout.tsx で CSS 変数化し Companies ページのツリー配下にのみ提供 - 既存 DB スキーマ (id/name/website/memo) はそのまま流用し、設計に存在し 実 DB にない項目 (業種・所在 / 状況 / 公開求人数 / 進行中候補数 / 最終接触日) は "—" プレースホルダーで描画 - 検索ボックス / フィルタチップは UI のみ (機能未配線) 他ページ・グローバル CSS・Tailwind 設定には影響なし。 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/app/companies/layout.tsx | 23 +- src/components/Companies.module.css | 347 ++++++++++++++++++++++++++++ src/components/Companies.tsx | 202 +++++++++------- 3 files changed, 484 insertions(+), 88 deletions(-) create mode 100644 src/components/Companies.module.css diff --git a/src/app/companies/layout.tsx b/src/app/companies/layout.tsx index 33d09c43..e95004e7 100644 --- a/src/app/companies/layout.tsx +++ b/src/app/companies/layout.tsx @@ -1,9 +1,28 @@ +import { Zen_Maru_Gothic, M_PLUS_Rounded_1c } from "next/font/google"; import MultiLayout from "@components/layouts/MulitLayout"; -export default function DashboardLayout({ +const zenMaru = Zen_Maru_Gothic({ + subsets: ["latin"], + weight: ["400", "500", "700", "900"], + display: "swap", + variable: "--font-zen-maru", +}); + +const mPlus = M_PLUS_Rounded_1c({ + subsets: ["latin"], + weight: ["400", "500", "700", "800"], + display: "swap", + variable: "--font-mplus-rounded", +}); + +export default function CompaniesLayout({ children, }: { children: React.ReactNode; }) { - return MultiLayout({ children }); + return ( +
+ {children} +
+ ); } diff --git a/src/components/Companies.module.css b/src/components/Companies.module.css new file mode 100644 index 00000000..147bf733 --- /dev/null +++ b/src/components/Companies.module.css @@ -0,0 +1,347 @@ +/* =================================================================== + * Companies — Fjord direction + * Scoped via CSS Modules. Tokens / classes are local-only; + * they do not affect any other page. + * =================================================================== */ + +.wrapper { + --ink: #0B2426; + --ink-2: #2B4448; + --ink-3: #5E7679; + --ink-4: #8FA2A4; + + --glacier: #F4F8F8; + --glacier-2: #EAF1F1; + --paper: #FFFFFF; + + --fjord: #0E4A50; + --fjord-2: #0A363B; + --mist: #6FA8AC; + --moss: #2D6E5A; + --amber: #B5772E; + --berry: #9B3247; + + --line: #D7E1E2; + --line-soft: #E5ECEC; + --focus: #6FA8AC; + + --r-sm: 4px; + --r: 6px; + --r-lg: 8px; + + --shadow-2: 0 1px 2px rgba(11,36,38,0.05), 0 12px 32px -16px rgba(11,36,38,0.16); + + font-family: var(--font-zen-maru), var(--font-mplus-rounded), -apple-system, + "Hiragino Sans", "Yu Gothic UI", Meiryo, system-ui, sans-serif; + font-weight: 500; + color: var(--ink); + background: var(--glacier); + line-height: 1.6; + min-height: 100%; + -webkit-font-smoothing: antialiased; +} + +.inner { + max-width: 1280px; + margin: 0 auto; + padding: 48px 56px 96px; +} + +/* ---- Page header ------------------------------------------------- */ +.pageHead { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 24px; + margin-bottom: 40px; +} +.tag { + font-family: var(--font-mplus-rounded), monospace; + font-size: 11.5px; + font-weight: 700; + color: var(--fjord); + letter-spacing: 0.16em; + text-transform: uppercase; + margin-bottom: 8px; +} +.pageHead h1 { + margin: 0; + font-size: 38px; + font-weight: 900; + letter-spacing: -0.02em; + color: var(--ink); +} +.sub { + margin: 8px 0 0; + font-size: 14.5px; + color: var(--ink-3); + line-height: 1.8; + max-width: 48ch; +} +.actions { + display: flex; + gap: 8px; + align-items: center; + flex-shrink: 0; +} + +/* ---- Buttons ----------------------------------------------------- */ +.btn { + display: inline-flex; + align-items: center; + gap: 8px; + height: 40px; + padding: 0 18px; + border-radius: var(--r); + border: 1px solid transparent; + font-family: inherit; + font-weight: 700; + font-size: 13.5px; + cursor: pointer; + transition: background 140ms ease, border-color 140ms ease, color 140ms ease; + text-decoration: none; + white-space: nowrap; +} +.btnPrimary { + background: var(--fjord); + color: #fff; + box-shadow: 0 6px 14px -8px rgba(14, 74, 80, 0.6); +} +.btnPrimary:hover { + background: var(--fjord-2); +} +.btnGhost { + background: var(--paper); + border-color: var(--line); + color: var(--ink-2); +} +.btnGhost:hover { + border-color: var(--ink-4); + color: var(--ink); +} + +/* ---- Toolbar (search + filter chips) ----------------------------- */ +.toolbar { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + margin-bottom: 20px; +} +.search { + max-width: 360px; + flex: 1; + position: relative; +} +.search input { + width: 100%; + height: 38px; + padding: 0 12px 0 38px; + background: var(--paper); + border: 1px solid var(--line); + border-radius: var(--r); + font-family: inherit; + font-weight: 500; + font-size: 14px; + color: var(--ink); + transition: border-color 140ms ease, box-shadow 140ms ease; +} +.search input::placeholder { + color: var(--ink-4); +} +.search input:focus { + outline: none; + border-color: var(--mist); + box-shadow: 0 0 0 4px rgba(111, 168, 172, 0.18); +} +.searchIcon { + position: absolute; + left: 12px; + top: 50%; + transform: translateY(-50%); + color: var(--ink-4); +} +.filterChip { + display: inline-flex; + align-items: center; + gap: 6px; + height: 32px; + padding: 0 12px; + border: 1px solid var(--line); + background: var(--paper); + border-radius: 999px; + font-size: 12.5px; + color: var(--ink-2); + cursor: pointer; + font-family: inherit; +} +.filterChip:hover { + border-color: var(--ink-4); + color: var(--ink); +} +.chipCaret { + color: var(--ink-4); +} + +/* ---- Panel + table ----------------------------------------------- */ +.panel { + background: var(--paper); + border: 1px solid var(--line); + border-radius: var(--r-lg); + overflow: hidden; +} +.list { + width: 100%; + border-collapse: separate; + border-spacing: 0; + font-size: 13.5px; +} +.list thead th { + text-align: left; + padding: 12px 16px; + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--ink-4); + background: var(--glacier-2); + border-bottom: 1px solid var(--line); + font-family: var(--font-mplus-rounded), monospace; +} +.list thead th:first-child { + border-top-left-radius: var(--r-lg); + padding-left: 24px; +} +.list thead th:last-child { + border-top-right-radius: var(--r-lg); + padding-right: 24px; +} +.list tbody td { + padding: 16px; + border-bottom: 1px solid var(--line-soft); + color: var(--ink); + vertical-align: middle; +} +.list tbody td:first-child { + padding-left: 24px; +} +.list tbody td:last-child { + padding-right: 24px; +} +.list tbody tr:hover td { + background: var(--glacier); +} +.list tbody tr:last-child td { + border-bottom: 0; +} +.muted { + color: var(--ink-3); + font-size: 12.5px; +} +.right { + text-align: right; +} +.num { + font-variant-numeric: tabular-nums; + font-family: var(--font-mplus-rounded), monospace; + font-weight: 700; +} + +/* ---- Company cell ------------------------------------------------ */ +.coCell { + display: flex; + align-items: center; + gap: 12px; +} +.coCell .logo { + width: 36px; + height: 36px; + border-radius: var(--r); + background: linear-gradient(135deg, var(--fjord), var(--mist)); + color: #fff; + display: grid; + place-items: center; + font-weight: 700; + font-family: var(--font-zen-maru), sans-serif; + font-size: 14px; + flex-shrink: 0; +} +.coName { + font-weight: 800; + letter-spacing: -0.01em; +} +.coSub { + font-size: 12px; + color: var(--ink-4); + margin-top: 1px; +} + +/* ---- Status badge ------------------------------------------------ */ +.status { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 3px 10px; + border-radius: 999px; + font-size: 11.5px; + font-weight: 700; + letter-spacing: 0.02em; + font-family: var(--font-zen-maru), sans-serif; +} +.statusDot { + width: 6px; + height: 6px; + border-radius: 50%; +} +.statusOpen { + background: rgba(45, 110, 90, 0.1); + color: var(--moss); +} +.statusOpen .statusDot { + background: var(--moss); +} +.statusPaused { + background: rgba(181, 119, 46, 0.12); + color: var(--amber); +} +.statusPaused .statusDot { + background: var(--amber); +} +.statusDraft { + background: var(--glacier-2); + color: var(--ink-3); +} +.statusDraft .statusDot { + background: var(--ink-4); +} + +/* ---- Row icon button -------------------------------------------- */ +.rowActions { + display: flex; + gap: 6px; + justify-content: flex-end; +} +.iconBtn { + width: 30px; + height: 30px; + display: grid; + place-items: center; + background: transparent; + border: 1px solid transparent; + border-radius: var(--r); + cursor: pointer; + color: var(--ink-2); + transition: background 140ms ease, border-color 140ms ease, color 140ms ease; +} +.iconBtn:hover { + background: var(--paper); + border-color: var(--line); + color: var(--ink); +} + +/* ---- Focus ring -------------------------------------------------- */ +.wrapper :where(a, button, input):focus-visible { + outline: 2px solid var(--focus); + outline-offset: 2px; + border-radius: var(--r-sm); +} diff --git a/src/components/Companies.tsx b/src/components/Companies.tsx index f497f871..03153597 100644 --- a/src/components/Companies.tsx +++ b/src/components/Companies.tsx @@ -1,125 +1,155 @@ import { useEffect, useState } from "react"; +import Link from "next/link"; import supabase from "../lib/supabase"; import { Database } from "../lib/database.types"; -import Link from "next/link"; +import styles from "./Companies.module.css"; type ICompany = Database["public"]["Tables"]["companies"]["Row"]; +function initial(name: string | null): string { + if (!name) return "—"; + const ch = name.replace(/^[((]?(株|有|合同会社|有限会社|株式会社)[))]?/u, "").trim()[0]; + return ch ?? "—"; +} + export default function Companies() { const [companies, setCompanies] = useState(); useEffect(() => { let cancelled = false; - (async () => { const { data } = await supabase.from("companies").select("*"); if (!cancelled && data) setCompanies(data); })(); - return () => { cancelled = true; }; }, []); - return ( - <> -
-

- 企業 -

+ const count = companies?.length ?? 0; -
-
- - 追加 + return ( +
+
+
+
+
COMPANIES
+

企業

+

+ 紹介先としてご縁のある {count} 社。最近のやりとりと、現在お預かりしている求人の数をひと目で。 +

+
+
+ + + + + + 企業を追加
-
-
-
-
- - - - - - - - - - - - {companies?.map((company) => { - return ; - })} - -
- Id - - Name - - Website - - Memo -
+
+
+ +
+ + + +
+ +
+ + + + + + + + + + + + + + {companies?.map((c) => ( + + ))} + +
企業業種・所在状況公開求人進行中候補最終接触
- +
); } -type CompanyProps = { - company: ICompany; -}; - -function Company({ company }: CompanyProps) { +function CompanyRow({ company }: { company: ICompany }) { return ( - - - {company.id} + + +
+
{initial(company.name)}
+
+
{company.name ?? "—"}
+ {company.memo &&
{company.memo}
} +
+
- - {company.name} + {company.website ?? "—"} + + + 取引中 + - - {company.website} + + - - {company.memo} + + - - - + — + +
+ +
); From c557481e96ae49805a142e7cb5c9416fcc3e46cc Mon Sep 17 00:00:00 2001 From: Masaki Komagata Date: Mon, 11 May 2026 20:19:26 +0900 Subject: [PATCH 2/2] =?UTF-8?q?CodeRabbit=E6=8C=87=E6=91=98=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C:=20a11y=20/=20=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=20/=20=E6=9C=AA?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E6=98=8E?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 検索 input に aria-label を付与 - Supabase 取得エラーを console.error で記録 - 未実装の CSV出力 / 編集ボタンを disabled + title で明示 Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/Companies.module.css | 11 ++++++++++ src/components/Companies.tsx | 32 ++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/components/Companies.module.css b/src/components/Companies.module.css index 147bf733..ba5968ab 100644 --- a/src/components/Companies.module.css +++ b/src/components/Companies.module.css @@ -119,6 +119,17 @@ border-color: var(--ink-4); color: var(--ink); } +.btn:disabled, +.iconBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.btn:disabled:hover, +.iconBtn:disabled:hover { + background: var(--paper); + border-color: var(--line); + color: var(--ink-2); +} /* ---- Toolbar (search + filter chips) ----------------------------- */ .toolbar { diff --git a/src/components/Companies.tsx b/src/components/Companies.tsx index 03153597..487e2376 100644 --- a/src/components/Companies.tsx +++ b/src/components/Companies.tsx @@ -18,8 +18,14 @@ export default function Companies() { useEffect(() => { let cancelled = false; (async () => { - const { data } = await supabase.from("companies").select("*"); - if (!cancelled && data) setCompanies(data); + const { data, error } = await supabase.from("companies").select("*"); + if (cancelled) return; + if (error) { + console.error("企業一覧の取得に失敗しました", error); + setCompanies([]); + return; + } + if (data) setCompanies(data); })(); return () => { cancelled = true; @@ -40,7 +46,13 @@ export default function Companies() {

- @@ -68,7 +80,10 @@ export default function Companies() { - +