initial commit

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
snc777
2026-05-09 14:24:17 +09:00
co-authored by Cursor
commit f6a781c47e
196 changed files with 31290 additions and 0 deletions
+42
View File
@@ -0,0 +1,42 @@
"use client";
/**
* App Router の Error Boundary。
*
* 主な発火経路は `app/page.tsx` の Zod `safeParse` 失敗(`data/*.json` の形式不正)。
* 受講生がデータを書き換えて壊した場合に「何が悪いか」を画面で読み取れるよう、
* `error.message` をそのまま表示し、`reset()` で再試行ボタンを出す。
*
* Next.js の規約により Client Component(`"use client"`)必須。
*/
import { Button } from "@/components/ui/button";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main className="flex min-h-screen flex-col items-center justify-center gap-6 bg-background px-6 py-10 text-foreground">
<div className="flex max-w-2xl flex-col gap-3 text-center">
<h1 className="text-lg font-semibold">読み込みに失敗しました</h1>
<p className="text-sm text-muted-foreground">
画面を表示するためのデータを準備できませんでした。
<br />
下のメッセージを参考に{" "}
<code className="rounded bg-muted px-1 py-0.5 text-xs">
data/*.json
</code>{" "}
を見直してください。
</p>
</div>
<pre className="max-h-64 max-w-2xl overflow-auto rounded-lg border border-border bg-card px-4 py-3 text-left text-xs leading-relaxed whitespace-pre-wrap text-foreground">
{error.message}
</pre>
<Button onClick={reset}>再読み込み</Button>
</main>
);
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+221
View File
@@ -0,0 +1,221 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
/* `--font-inter` は next/font/google(app/layout.tsx)が <html> に注入する CSS 変数。
欧文・数字は Inter、日本語はシステムの日本語フォントに自然にフォールバックする。 */
--font-sans:
var(--font-inter), "Hiragino Sans", "BIZ UDPGothic", "Yu Gothic UI",
system-ui, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
/* font-sans と同じ。見出し専用フォントに切り替える場合はここを変更 */
--font-heading: var(--font-sans);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-canvas: var(--canvas);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
--font-serif: "Georgia", serif;
--radius: 0.625rem;
--tracking-tighter: calc(var(--tracking-normal) - 0.05em);
--tracking-tight: calc(var(--tracking-normal) - 0.025em);
--tracking-wide: calc(var(--tracking-normal) + 0.025em);
--tracking-wider: calc(var(--tracking-normal) + 0.05em);
--tracking-widest: calc(var(--tracking-normal) + 0.1em);
--tracking-normal: var(--tracking-normal);
--shadow-2xl: var(--shadow-2xl);
--shadow-xl: var(--shadow-xl);
--shadow-lg: var(--shadow-lg);
--shadow-md: var(--shadow-md);
--shadow: var(--shadow);
--shadow-sm: var(--shadow-sm);
--shadow-xs: var(--shadow-xs);
--shadow-2xs: var(--shadow-2xs);
--spacing: var(--spacing);
--letter-spacing: var(--letter-spacing);
--shadow-offset-y: var(--shadow-offset-y);
--shadow-offset-x: var(--shadow-offset-x);
--shadow-spread: var(--shadow-spread);
--shadow-blur: var(--shadow-blur);
--shadow-opacity: var(--shadow-opacity);
--color-shadow-color: var(--shadow-color);
--color-destructive-foreground: var(--destructive-foreground);
}
:root {
--background: oklch(0.9701 0.0041 91.4464);
--foreground: oklch(0.2354 0.0019 286.2536);
--card: oklch(0.9972 0.0028 84.5587);
--card-foreground: oklch(0.2354 0.0019 286.2536);
--canvas: oklch(0.985 0.005 84);
--popover: oklch(0.9972 0.0028 84.5587);
--popover-foreground: oklch(0.2354 0.0019 286.2536);
--primary: oklch(0.3732 0.0635 258.276);
--primary-foreground: oklch(0.9816 0.0017 247.839);
--secondary: oklch(0.9526 0.0058 84.5665);
--secondary-foreground: oklch(0.3732 0.0635 258.276);
--muted: oklch(0.9468 0.0046 78.2976);
--muted-foreground: oklch(0.522 0.004 67.7376);
--accent: oklch(0.9526 0.0058 84.5665);
--accent-foreground: oklch(0.3732 0.0635 258.276);
--destructive: oklch(0.5624 0.0956 20.5303);
--border: oklch(0.9251 0.0071 88.645);
--input: oklch(0.9251 0.0071 88.645);
--ring: oklch(0.3732 0.0635 258.276);
--chart-1: oklch(0.6318 0.06 244.8148);
--chart-2: oklch(0.6441 0.0496 146.8813);
--chart-3: oklch(0.719 0.1077 74.6093);
--chart-4: oklch(0.7107 0.0351 256.7878);
--chart-5: oklch(0.7118 0.0129 286.0665);
--radius: 0.625rem;
--sidebar: oklch(0.9526 0.0058 84.5665);
--sidebar-foreground: oklch(0.2354 0.0019 286.2536);
--sidebar-primary: oklch(0.3732 0.0635 258.276);
--sidebar-primary-foreground: oklch(0.9816 0.0017 247.839);
--sidebar-accent: oklch(0.9251 0.0071 88.645);
--sidebar-accent-foreground: oklch(0.3732 0.0635 258.276);
--sidebar-border: oklch(0.9039 0.0029 84.5597);
--sidebar-ring: oklch(0.3732 0.0635 258.276);
--destructive-foreground: oklch(1 0 0);
--font-sans:
var(--font-inter), "Hiragino Sans", "BIZ UDPGothic", "Yu Gothic UI",
system-ui, sans-serif;
--font-serif: "Georgia", serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--shadow-color: #000000;
--shadow-opacity: 0.05;
--shadow-blur: 12px;
--shadow-spread: 0px;
--shadow-offset-x: 0px;
--shadow-offset-y: 4px;
--letter-spacing: 0;
--spacing: 0.25rem;
--shadow-2xs: 0px 4px 12px 0px hsl(0 0% 0% / 0.03);
--shadow-xs: 0px 4px 12px 0px hsl(0 0% 0% / 0.03);
--shadow-sm:
0px 4px 12px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);
--shadow:
0px 4px 12px 0px hsl(0 0% 0% / 0.05), 0px 1px 2px -1px hsl(0 0% 0% / 0.05);
--shadow-md:
0px 4px 12px 0px hsl(0 0% 0% / 0.05), 0px 2px 4px -1px hsl(0 0% 0% / 0.05);
--shadow-lg:
0px 4px 12px 0px hsl(0 0% 0% / 0.05), 0px 4px 6px -1px hsl(0 0% 0% / 0.05);
--shadow-xl:
0px 4px 12px 0px hsl(0 0% 0% / 0.05), 0px 8px 10px -1px hsl(0 0% 0% / 0.05);
--shadow-2xl: 0px 4px 12px 0px hsl(0 0% 0% / 0.13);
--tracking-normal: 0;
}
.dark {
--background: oklch(0.2252 0.0049 248.0473);
--foreground: oklch(0.9168 0.0035 67.7802);
--card: oklch(0.2668 0.0071 248.094);
--card-foreground: oklch(0.9168 0.0035 67.7802);
--canvas: oklch(0.246 0.006 248.07);
--popover: oklch(0.2668 0.0071 248.094);
--popover-foreground: oklch(0.9168 0.0035 67.7802);
--primary: oklch(0.7441 0.1311 264.2789);
--primary-foreground: oklch(0.2077 0.0398 265.7549);
--secondary: oklch(0.2972 0 0);
--secondary-foreground: oklch(0.9168 0.0035 67.7802);
--muted: oklch(0.2972 0 0);
--muted-foreground: oklch(0.709 0 0);
--accent: oklch(0.2972 0 0);
--accent-foreground: oklch(0.9168 0.0035 67.7802);
--destructive: oklch(0.6338 0.1208 21.0422);
--border: oklch(0.3211 0 0);
--input: oklch(0.3211 0 0);
--ring: oklch(0.7441 0.1311 264.2789);
--chart-1: oklch(0.6318 0.06 244.8148);
--chart-2: oklch(0.6441 0.0496 146.8813);
--chart-3: oklch(0.719 0.1077 74.6093);
--chart-4: oklch(0.7107 0.0351 256.7878);
--chart-5: oklch(0.7118 0.0129 286.0665);
--sidebar: oklch(0.1989 0.0051 248.0841);
--sidebar-foreground: oklch(0.9168 0.0035 67.7802);
--sidebar-primary: oklch(0.7441 0.1311 264.2789);
--sidebar-primary-foreground: oklch(0.2077 0.0398 265.7549);
--sidebar-accent: oklch(0.2972 0 0);
--sidebar-accent-foreground: oklch(0.9168 0.0035 67.7802);
--sidebar-border: oklch(0.3211 0 0);
--sidebar-ring: oklch(0.7441 0.1311 264.2789);
--destructive-foreground: oklch(1 0 0);
--radius: 0.625rem;
--font-sans:
var(--font-inter), "Hiragino Sans", "BIZ UDPGothic", "Yu Gothic UI",
system-ui, sans-serif;
--font-serif: "Georgia", serif;
--font-mono: "JetBrains Mono", ui-monospace, monospace;
--shadow-color: #000000;
--shadow-opacity: 0.3;
--shadow-blur: 20px;
--shadow-spread: 0px;
--shadow-offset-x: 0px;
--shadow-offset-y: 10px;
--letter-spacing: 0;
--spacing: 0.25rem;
--shadow-2xs: 0px 10px 20px 0px hsl(0 0% 0% / 0.15);
--shadow-xs: 0px 10px 20px 0px hsl(0 0% 0% / 0.15);
--shadow-sm:
0px 10px 20px 0px hsl(0 0% 0% / 0.3), 0px 1px 2px -1px hsl(0 0% 0% / 0.3);
--shadow:
0px 10px 20px 0px hsl(0 0% 0% / 0.3), 0px 1px 2px -1px hsl(0 0% 0% / 0.3);
--shadow-md:
0px 10px 20px 0px hsl(0 0% 0% / 0.3), 0px 2px 4px -1px hsl(0 0% 0% / 0.3);
--shadow-lg:
0px 10px 20px 0px hsl(0 0% 0% / 0.3), 0px 4px 6px -1px hsl(0 0% 0% / 0.3);
--shadow-xl:
0px 10px 20px 0px hsl(0 0% 0% / 0.3), 0px 8px 10px -1px hsl(0 0% 0% / 0.3);
--shadow-2xl: 0px 10px 20px 0px hsl(0 0% 0% / 0.75);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
letter-spacing: var(--tracking-normal);
line-height: 1.65;
}
html {
@apply font-sans;
}
}
+38
View File
@@ -0,0 +1,38 @@
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { TooltipProvider } from "@/components/ui/tooltip";
// Inter は欧文・数字部分にだけ適用したい(日本語はシステム日本語フォントに任せる)。
// variable で `--font-inter` を発行し、`globals.css` の `--font-sans` で参照する。
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
});
export const metadata: Metadata = {
title: "採用管理ワークスペース",
description: "tweakcn テーマ + 日本語タイポ検証用プロトタイプ",
};
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ja" className={`${inter.variable} h-full antialiased`}>
<body className="flex min-h-full flex-col">
{/* shadcn/ui の Sidebar コンポーネント(SidebarMenuButton の collapsed
時 tooltip 等)が要求するためアプリ全体をラップする。 */}
<TooltipProvider delay={300}>{children}</TooltipProvider>
</body>
</html>
);
}
+35
View File
@@ -0,0 +1,35 @@
import { Workspace } from "@/components/workspace/Workspace";
import positionsData from "@/data/positions.json";
import candidatesData from "@/data/candidates.json";
import workspaceData from "@/data/workspace.json";
import {
departmentsSchema,
candidatesSchema,
workspaceSchema,
} from "@/lib/schema";
export default function Page() {
const deptResult = departmentsSchema.safeParse(positionsData);
const candResult = candidatesSchema.safeParse(candidatesData);
const wsResult = workspaceSchema.safeParse(workspaceData);
if (!deptResult.success || !candResult.success || !wsResult.success) {
const errors = [
!deptResult.success &&
`positions.json: ${deptResult.error.issues[0]?.message}`,
!candResult.success &&
`candidates.json: ${candResult.error.issues[0]?.message}`,
!wsResult.success &&
`workspace.json: ${wsResult.error.issues[0]?.message}`,
].filter(Boolean);
throw new Error(`データの形式が正しくありません:\n${errors.join("\n")}`);
}
return (
<Workspace
initialDepartments={deptResult.data}
initialCandidates={candResult.data}
workspace={wsResult.data}
/>
);
}