diff --git a/backend/alembic_migrations/versions/0037_merge_project_caf_into_description.py b/backend/alembic_migrations/versions/0037_merge_project_caf_into_description.py
new file mode 100644
index 00000000..327a603c
--- /dev/null
+++ b/backend/alembic_migrations/versions/0037_merge_project_caf_into_description.py
@@ -0,0 +1,50 @@
+"""merge resume_projects challenge/action/result into description(課題・行動・成果の統合)
+
+プロジェクトの「課題」「行動」「成果」は分割する必要がないため、単一の自由記述欄
+「詳細」(description) に統合した。これに伴い resume_projects の challenge / action /
+result カラムを削除し、description カラムを新設する。
+
+統合対象の既存本番データは存在しないため、データ移行(3 カラムの連結)は行わない。
+
+libSQL (SQLite 互換) は ALTER COLUMN / DROP COLUMN を直接サポートしないため、
+batch_alter_table(テーブル再作成)でカラムを入れ替える。
+
+Revision ID: 0037_merge_project_caf_into_description
+Revises: 0036_rename_github_analysis_to_github_link
+Create Date: 2026-05-27 00:00:00.000000
+"""
+
+from typing import Sequence, Union
+
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "0037_merge_project_caf_into_description"
+down_revision: Union[str, None] = "0036_rename_github_analysis_to_github_link"
+branch_labels: Union[str, Sequence[str], None] = None
+depends_on: Union[str, Sequence[str], None] = None
+
+
+def upgrade() -> None:
+ with op.batch_alter_table("resume_projects") as batch_op:
+ batch_op.add_column(
+ sa.Column("description", sa.Text(), nullable=False, server_default=""),
+ )
+ batch_op.drop_column("challenge")
+ batch_op.drop_column("action")
+ batch_op.drop_column("result")
+
+
+def downgrade() -> None:
+ with op.batch_alter_table("resume_projects") as batch_op:
+ batch_op.add_column(
+ sa.Column("challenge", sa.Text(), nullable=False, server_default=""),
+ )
+ batch_op.add_column(
+ sa.Column("action", sa.Text(), nullable=False, server_default=""),
+ )
+ batch_op.add_column(
+ sa.Column("result", sa.Text(), nullable=False, server_default=""),
+ )
+ batch_op.drop_column("description")
diff --git a/backend/app/models/resume.py b/backend/app/models/resume.py
index 6f574962..0211fa96 100644
--- a/backend/app/models/resume.py
+++ b/backend/app/models/resume.py
@@ -167,9 +167,8 @@ class ResumeProject(Base):
end_date_value: Mapped[date | None] = mapped_column("end_date", Date, nullable=True)
is_current: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
role: Mapped[str] = mapped_column(String(200), nullable=False, default="")
- challenge: Mapped[str] = mapped_column(Text, nullable=False, default="")
- action: Mapped[str] = mapped_column(Text, nullable=False, default="")
- result: Mapped[str] = mapped_column(Text, nullable=False, default="")
+ # 課題・行動・成果を統合した自由記述欄。見出しは「詳細」。
+ description: Mapped[str] = mapped_column(Text, nullable=False, default="")
team_total: Mapped[str] = mapped_column(String(60), nullable=False, default="")
team_member_rows: Mapped[list["ResumeProjectTeamMember"]] = relationship(
back_populates="project",
diff --git a/backend/app/repositories/resume.py b/backend/app/repositories/resume.py
index f777790b..ebe96b41 100644
--- a/backend/app/repositories/resume.py
+++ b/backend/app/repositories/resume.py
@@ -105,9 +105,7 @@ def _build_project_row(self, index: int, payload: dict[str, object]) -> ResumePr
),
is_current=payload.get("is_current", False),
role=payload.get("role", ""),
- challenge=payload.get("challenge", ""),
- action=payload.get("action", ""),
- result=payload.get("result", ""),
+ description=payload.get("description", ""),
team_total=team.get("total", ""),
team_member_rows=[
ResumeProjectTeamMember(
diff --git a/backend/app/schemas/resume.py b/backend/app/schemas/resume.py
index 6dc13960..40bbe11f 100644
--- a/backend/app/schemas/resume.py
+++ b/backend/app/schemas/resume.py
@@ -74,9 +74,8 @@ class Project(BaseModel):
end_date: str = Field(default="", max_length=30)
is_current: bool = False
role: str = Field(max_length=200, default="")
- challenge: str = Field(max_length=1500, default="")
- action: str = Field(max_length=1500, default="")
- result: str = Field(max_length=1500, default="")
+ # 課題・行動・成果を統合した自由記述欄(見出し「詳細」)
+ description: str = Field(max_length=4500, default="")
team: ProjectTeam = Field(default_factory=ProjectTeam)
technology_stacks: list[TechnologyStackItem] = Field(default_factory=list)
phases: list[str] = Field(default_factory=list)
diff --git a/backend/app/services/markdown/generators/resume_generator.py b/backend/app/services/markdown/generators/resume_generator.py
index 9962da03..ee2e23f8 100644
--- a/backend/app/services/markdown/generators/resume_generator.py
+++ b/backend/app/services/markdown/generators/resume_generator.py
@@ -84,15 +84,9 @@ def build_resume_markdown(payload: dict[str, Any]) -> str:
role = _a(proj, "role")
if role:
lines.append(field_line("担当", role))
- challenge = _a(proj, "challenge")
- if challenge:
- lines.append(field_line("課題", challenge))
- action = _a(proj, "action")
- if action:
- lines.append(field_line("行動", action))
- result = _a(proj, "result")
- if result:
- lines.append(field_line("成果", result))
+ description = _a(proj, "description")
+ if description:
+ lines.append(field_line("詳細", description))
# 体制(後方互換: 旧 scale → team の正規化は shared に集約)
team = normalize_team(proj)
if team:
diff --git a/backend/app/services/pdf/generators/resume_generator.py b/backend/app/services/pdf/generators/resume_generator.py
index 8a7e1a42..b4d87e07 100644
--- a/backend/app/services/pdf/generators/resume_generator.py
+++ b/backend/app/services/pdf/generators/resume_generator.py
@@ -78,15 +78,9 @@ def _build_project_html(project) -> str:
# 左カラム: 業務内容
left_parts: list[str] = []
- challenge = _a(project, "challenge")
- if challenge:
- left_parts.append(f"【課題】{_md(challenge)}")
- action = _a(project, "action")
- if action:
- left_parts.append(f"【行動】{_md(action)}")
- result = _a(project, "result")
- if result:
- left_parts.append(f"【成果】{_md(result)}")
+ description = _a(project, "description")
+ if description:
+ left_parts.append(f"【詳細】{_md(description)}")
left_content = "".join(left_parts) if left_parts else "-"
# 右カラム: 開発環境(技術スタック)
diff --git a/backend/tests/test_delete_documents.py b/backend/tests/test_delete_documents.py
index e8b9e1dc..0f0d1c56 100644
--- a/backend/tests/test_delete_documents.py
+++ b/backend/tests/test_delete_documents.py
@@ -29,9 +29,7 @@
"end_date": "2022-03",
"is_current": False,
"role": "SE",
- "challenge": "",
- "action": "",
- "result": "",
+ "description": "",
"team": {"total": "5", "members": []},
"technology_stacks": [],
"phases": [],
diff --git a/backend/tests/test_endpoints.py b/backend/tests/test_endpoints.py
index ee9d75f3..96f29848 100644
--- a/backend/tests/test_endpoints.py
+++ b/backend/tests/test_endpoints.py
@@ -140,9 +140,7 @@ def test_resume_round_trips_nested_structure(client: TestClient) -> None:
"end_date": "2024-03",
"is_current": False,
"role": "SE",
- "challenge": "性能改善",
- "action": "非同期化",
- "result": "応答時間短縮",
+ "description": "性能改善のため非同期化し応答時間を短縮",
"team": {
"total": "5",
"members": [{"role": "SE", "count": 3}],
diff --git a/backend/tests/test_schemas.py b/backend/tests/test_schemas.py
index 5d729765..b03cc774 100644
--- a/backend/tests/test_schemas.py
+++ b/backend/tests/test_schemas.py
@@ -26,10 +26,7 @@ def experience_payload() -> dict:
"end_date": "2024-03",
"is_current": False,
"role": "メンバー",
- "description": "API開発",
- "challenge": "課題",
- "action": "行動",
- "result": "処理速度を改善",
+ "description": "課題・行動・成果をまとめた詳細",
"team": {
"total": "5",
"members": [
diff --git a/frontend/e2e/career-dirty-indicator.spec.ts b/frontend/e2e/career-dirty-indicator.spec.ts
index bea316bb..3ea29b10 100644
--- a/frontend/e2e/career-dirty-indicator.spec.ts
+++ b/frontend/e2e/career-dirty-indicator.spec.ts
@@ -104,9 +104,7 @@ test.describe("職務経歴書 未保存マーク", () => {
end_date: "2022-03",
is_current: false,
role: "Eng",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "5", members: [] },
technology_stacks: [],
phases: [],
diff --git a/frontend/src/App.module.css b/frontend/src/App.module.css
index 4fdc30a2..e6ba56fd 100644
--- a/frontend/src/App.module.css
+++ b/frontend/src/App.module.css
@@ -5,7 +5,7 @@
}
.sidebar {
- width: 220px;
+ width: var(--sidebar-width);
background: var(--sidebar-bg);
color: #fff;
display: flex;
@@ -73,7 +73,7 @@
.mainContent {
flex: 1;
- margin-left: 220px;
+ margin-left: var(--sidebar-width);
overflow-y: auto;
height: 100vh;
background: var(--bg-page);
@@ -135,6 +135,6 @@
.mainContent {
margin-left: 0;
- margin-top: 48px;
+ margin-top: var(--topbar-height);
}
}
diff --git a/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx b/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
index 0a3749fa..3e2c6226 100644
--- a/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
+++ b/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx
@@ -163,6 +163,7 @@ export function CareerExperienceEditor({
onUpdateExperienceField(expIndex, "employee_count", e.target.value)
@@ -180,6 +181,7 @@ export function CareerExperienceEditor({
onUpdateExperienceField(expIndex, "capital", e.target.value)}
placeholder="例: 5"
diff --git a/frontend/src/components/forms/CareerResumeForm.module.css b/frontend/src/components/forms/CareerResumeForm.module.css
index 9758f87b..fec443a2 100644
--- a/frontend/src/components/forms/CareerResumeForm.module.css
+++ b/frontend/src/components/forms/CareerResumeForm.module.css
@@ -125,7 +125,7 @@
}
.splitter:hover {
- background: #b7c5e0;
+ background: var(--accent);
}
/* 右: PDF カラム(幅は CSS 変数 --pdf-col-width で可変。中の panel が縦に伸びる)。
@@ -140,17 +140,6 @@
padding-left: 1rem;
}
-/* 最大化中: フォームとスプリッターを隠し、PDF をフォームに覆い被せて全幅表示する */
-.maximized .formCol,
-.maximized .splitter {
- display: none;
-}
-
-.maximized .pdfCol {
- flex: 1;
- width: auto;
-}
-
/* 横幅が足りない時(2 カラムを並べると最小幅を割る幅)は縦積みに切り替える。
閾値 = PDF 最小(280) + フォーム最小(360) + gap/splitter(38) ≒ 680px。
これ未満では左右に並べず、フォームの下に PDF を全幅で積む(要素が切れない)。 */
diff --git a/frontend/src/components/forms/CareerResumeForm.tsx b/frontend/src/components/forms/CareerResumeForm.tsx
index 19cd081d..0970ee5e 100644
--- a/frontend/src/components/forms/CareerResumeForm.tsx
+++ b/frontend/src/components/forms/CareerResumeForm.tsx
@@ -35,12 +35,7 @@ export function CareerResumeForm() {
const assist = useResumeImportAssist();
const pdfInputRef = useRef(null);
const splitRef = useRef(null);
- const {
- width: pdfWidth,
- maximized: pdfMaximized,
- startResize,
- toggleMaximize,
- } = usePdfPanelLayout(splitRef);
+ const { width: pdfWidth, startResize } = usePdfPanelLayout(splitRef);
const {
form,
setForm,
@@ -180,14 +175,14 @@ export function CareerResumeForm() {
- {/* 左=入力フォーム / 右=PDF 原本ビュー。スプリッターで左右リサイズ、最大化で覆い被せる。
+ {/* 左=入力フォーム / 右=PDF 原本ビュー。スプリッターで左右リサイズする。
splitWrap はコンテナクエリの基準(= split に割り当てられる実幅)。
横幅が足りない時は split を縦積みに切り替える。PDF カラム幅は CSS 変数で渡し、
縦積み時は CSS 側で全幅に上書きする(inline style だと上書きできないため変数経由)。 */}
{/* 左: 入力フォーム(選択中フィールドは緑枠 = import-assign-form の :focus CSS) */}
@@ -257,11 +252,7 @@ export function CareerResumeForm() {
{/* 右: PDF 原本ビュー(独立スクロール)。文字を選択して入力欄へ流し込む。
幅は CSS 変数 --pdf-col-width を CSS 側で参照(縦積み時は全幅へ上書き)。 */}
diff --git a/frontend/src/components/forms/ProjectModal.module.css b/frontend/src/components/forms/ProjectModal.module.css
index 6e4de69e..57710055 100644
--- a/frontend/src/components/forms/ProjectModal.module.css
+++ b/frontend/src/components/forms/ProjectModal.module.css
@@ -1,30 +1,38 @@
+/* サイドバーを覆わず(操作可能なまま残し)、その右のコンテンツ領域いっぱいにモーダルを開く。
+ overlay 自体を left:サイドバー幅 から始めるので、z-index がサイドバー(100)より上でも
+ サイドバーは露出したままになる。 */
.overlay {
position: fixed;
inset: 0;
+ left: var(--sidebar-width);
background: var(--overlay-bg);
display: flex;
- align-items: center;
- justify-content: center;
+ padding: 1.2rem;
z-index: 1000;
}
+/* モーダルはコンテンツ領域いっぱいに広げる(職務経歴書ページと同等の作業領域を確保)。 */
.modal {
background: var(--bg-card);
border-radius: 12px;
- width: 90vw;
- max-width: 800px;
- max-height: 85vh;
+ width: 100%;
+ height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
-/* 取り込みパネルを併置する分だけ横幅を広げる */
-.modalWide {
- max-width: 1150px;
+/* モバイルはサイドバーが上部バーになるため、その下から全幅で開く。 */
+@media (max-width: 768px) {
+ .overlay {
+ left: 0;
+ top: var(--topbar-height);
+ padding: 0.8rem;
+ }
}
-/* ヘッダー下の領域。左=入力フォーム / 右=取り込みブロックの 2 カラム */
+/* ヘッダー下の領域。左=入力フォーム / 右=取り込みブロックの 2 カラム。
+ スプリッターのドラッグで左右の比率を変えられる。 */
.bodyWrap {
flex: 1;
display: flex;
@@ -32,9 +40,26 @@
overflow: hidden;
}
-/* モーダル内に再掲する PDF 原本ビュー(タブ固定・ページ部のみスクロール) */
+/* 入力フォームと PDF カラムの境界。ドラッグで左右比率を変える。 */
+.splitter {
+ flex-shrink: 0;
+ align-self: stretch;
+ width: 6px;
+ border-radius: 3px;
+ background: var(--border);
+ cursor: col-resize;
+ transition: background 0.15s;
+}
+
+.splitter:hover {
+ background: var(--accent);
+}
+
+/* モーダル内に再掲する PDF 原本ビュー(タブ固定・ページ部のみスクロール)。
+ 幅は CSS 変数 --pdf-col-width で可変(縦積み時は下のメディアクエリで全幅に上書き)。
+ inline style ではなく変数経由にすることで、縦積み時の全幅化を CSS 側で上書きできる。 */
.blocksColumn {
- width: 320px;
+ width: var(--pdf-col-width, 320px);
flex-shrink: 0;
display: flex;
flex-direction: column;
@@ -44,6 +69,33 @@
padding: 1rem;
}
+/* 横幅が狭い時は左右 2 カラムをやめ、フォームの下に PDF を全幅で縦積みする。
+ モーダルは width:90vw なのでビューポート幅で判定する。 */
+@media (max-width: 700px) {
+ .bodyWrap {
+ flex-direction: column;
+ overflow-y: auto;
+ }
+
+ /* 縦積み時はモーダル全体スクロールに委ねる(カラム個別スクロールを解除) */
+ .body {
+ overflow-y: visible;
+ }
+
+ /* 縦積み時はドラッグでの左右リサイズを無効化 */
+ .splitter {
+ display: none;
+ }
+
+ /* 幅指定を上書きして全幅。区切りは左ボーダーから上ボーダーへ。 */
+ .blocksColumn {
+ width: 100%;
+ min-height: 60vh;
+ border-left: none;
+ border-top: 1px solid var(--border);
+ }
+}
+
.header {
display: flex;
justify-content: space-between;
diff --git a/frontend/src/components/forms/ProjectModal.test.tsx b/frontend/src/components/forms/ProjectModal.test.tsx
index fd2c5960..669d38bb 100644
--- a/frontend/src/components/forms/ProjectModal.test.tsx
+++ b/frontend/src/components/forms/ProjectModal.test.tsx
@@ -17,9 +17,7 @@ const invalidDateProject: CareerProjectForm = {
end_date: "2024-01",
is_current: false,
role: "エンジニア",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "", members: [] },
technology_stacks: [],
phases: [],
@@ -31,9 +29,7 @@ const emptyProject: CareerProjectForm = {
end_date: "",
is_current: false,
role: "",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "", members: [] },
technology_stacks: [],
phases: [],
diff --git a/frontend/src/components/forms/ProjectModal.tsx b/frontend/src/components/forms/ProjectModal.tsx
index 7efc00ea..0875f0ba 100644
--- a/frontend/src/components/forms/ProjectModal.tsx
+++ b/frontend/src/components/forms/ProjectModal.tsx
@@ -1,3 +1,5 @@
+import { useRef, type CSSProperties } from "react";
+
import type { CareerProjectForm } from "../../payloadBuilders";
import {
careerTechnologyStackCategories,
@@ -7,6 +9,7 @@ import {
} from "../../constants";
import { useProjectFormDirty } from "../../hooks/career/useProjectFormDirty";
import { useProjectModalForm } from "../../hooks/career/useProjectModalForm";
+import { usePdfPanelLayout } from "../../hooks/career/usePdfPanelLayout";
import type { UseResumeImportAssistReturn } from "../../hooks/career/useResumeImportAssist";
import { Combobox } from "./Combobox";
import { MarkdownTextarea } from "./MarkdownTextarea";
@@ -58,12 +61,19 @@ export function ProjectModal({
/** PDF が選択されている時だけモーダル内に原本ビューを表示する */
const showPdf = !!assist && !!assist.file;
+ // 入力フォームと PDF カラムの比率をスプリッターのドラッグで変える。
+ // PDF カラムは右側なので「幅 = コンテナ右端 - マウス X」。reservedGap はカラム間のスプリッター(6px)。
+ const bodyWrapRef = useRef
(null);
+ const { width: pdfWidth, startResize } = usePdfPanelLayout(bodyWrapRef, {
+ initialWidth: 440,
+ minWidth: 240,
+ minFormWidth: 320,
+ reservedGap: 6,
+ });
+
return (
-
e.stopPropagation()}
- >
+
e.stopPropagation()}>
{project ? "プロジェクト編集" : "プロジェクト追加"}
@@ -84,7 +94,11 @@ export function ProjectModal({
-
+
{showPdf && assist && (
-
+ <>
+ {/* 入力フォームと PDF カラムの境界。ドラッグで左右比率を変える(縦積み時は CSS で非表示)。 */}
+
+
+ >
)}
diff --git a/frontend/src/components/forms/ResumePdfTracePanel.module.css b/frontend/src/components/forms/ResumePdfTracePanel.module.css
index 1d7814ab..e280b2e4 100644
--- a/frontend/src/components/forms/ResumePdfTracePanel.module.css
+++ b/frontend/src/components/forms/ResumePdfTracePanel.module.css
@@ -7,7 +7,7 @@
min-width: 0;
}
-/* VSCode タブ風ヘッダー(ファイル名 + ズーム + 最大化)。常に上部に固定。 */
+/* VSCode タブ風ヘッダー(ファイル名 + ズーム)。常に上部に固定。 */
.tab {
display: flex;
align-items: center;
@@ -16,15 +16,15 @@
flex-shrink: 0;
padding: 0.3rem 0.5rem;
margin-bottom: 0.5rem;
- background: #f1f3f5;
- border: 1px solid #d0d7de;
+ background: var(--bg-header);
+ border: 1px solid var(--border);
border-radius: 6px;
user-select: none;
}
.tabName {
font-size: 0.8rem;
- color: #1a1a1a;
+ color: var(--text-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -39,23 +39,26 @@
.zoomLabel {
font-size: 0.72rem;
- color: #444;
+ color: var(--text-muted);
min-width: 2.8em;
text-align: center;
}
+/* ズームボタン。グローバルの button { color:#fff } を打ち消すため color を明示する
+ (未指定だと白背景×白文字で見えなくなる)。 */
.iconButton {
font-size: 0.8rem;
line-height: 1;
padding: 0.15rem 0.45rem;
- border: 1px solid #c0c6cc;
+ border: 1px solid var(--border-input);
border-radius: 4px;
- background: #fff;
+ background: var(--bg-input);
+ color: var(--text-primary);
cursor: pointer;
}
.iconButton:hover {
- background: #eef1f4;
+ background: var(--ghost-hover);
}
.iconButton:disabled {
@@ -65,7 +68,7 @@
.hint {
font-size: 0.8rem;
- color: #333;
+ color: var(--text-secondary);
margin: 0 0 0.5rem;
flex-shrink: 0;
}
@@ -78,15 +81,15 @@
.empty {
font-size: 0.85rem;
- color: #666;
+ color: var(--text-muted);
}
/* スキャン PDF(テキストレイヤーなし)の警告 */
.warning {
font-size: 0.85rem;
- color: #b45309;
- background: #fff7ed;
- border: 1px solid #fed7aa;
+ color: var(--warning-text);
+ background: var(--warning-bg);
+ border: 1px solid var(--warning-border);
border-radius: 6px;
padding: 0.4rem 0.5rem;
margin: 0 0 0.6rem;
@@ -106,7 +109,7 @@
/* react-pdf の各ページ(canvas + テキストレイヤー)。選択しやすいよう枠で区切る。 */
.page {
- border: 1px solid #e5e5e5;
+ border: 1px solid var(--border);
border-radius: 4px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
diff --git a/frontend/src/components/forms/ResumePdfTracePanel.tsx b/frontend/src/components/forms/ResumePdfTracePanel.tsx
index 66fa1d43..36af58ac 100644
--- a/frontend/src/components/forms/ResumePdfTracePanel.tsx
+++ b/frontend/src/components/forms/ResumePdfTracePanel.tsx
@@ -14,10 +14,6 @@ const ZOOM_STEP = 0.25;
type Props = {
assist: UseResumeImportAssistReturn;
- /** 最大化(フォームに覆い被せて全幅表示)中か。タブのラベル切替に使う。 */
- maximized?: boolean;
- /** 最大化トグル。渡された時だけタブに最大化ボタン/ダブルクリックを出す(モーダルでは渡さない)。 */
- onToggleMaximize?: () => void;
};
/**
@@ -25,9 +21,9 @@ type Props = {
*
* 選択された PDF を原本のまま描画し、ユーザーが文字をドラッグ選択すると、選択中(緑枠)の
* 入力欄へ流し込む。「どの項目をどの粒度で取り込むか」は人間が PDF 上の選択で決める。
- * 上部のタブ風ヘッダーでズーム(拡大/縮小)と最大化(フォームに覆い被せる)を操作する。
+ * 上部のタブ風ヘッダーでズーム(拡大/縮小)を操作する。
*/
-export function ResumePdfTracePanel({ assist, maximized = false, onToggleMaximize }: Props) {
+export function ResumePdfTracePanel({ assist }: Props) {
const { file, fileName, error, fillSelection, setError } = assist;
const [zoom, setZoom] = useState(1);
@@ -38,12 +34,7 @@ export function ResumePdfTracePanel({ assist, maximized = false, onToggleMaximiz
{file && (
-
+
{fileName ?? IMPORT_ASSIST_MESSAGES.TAB_FALLBACK}
@@ -66,11 +57,6 @@ export function ResumePdfTracePanel({ assist, maximized = false, onToggleMaximiz
>
+
- {onToggleMaximize && (
-
- )}
)}
diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts
index d577c7a3..b6f46b0b 100644
--- a/frontend/src/constants.ts
+++ b/frontend/src/constants.ts
@@ -86,9 +86,7 @@ export const blankCareerProject: CareerProjectForm = {
end_date: "",
is_current: false,
role: "",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "", members: [] },
technology_stacks: [{ ...blankCareerTechnologyStack }],
phases: [],
diff --git a/frontend/src/constants/messages.ts b/frontend/src/constants/messages.ts
index 22e93fa0..872428ce 100644
--- a/frontend/src/constants/messages.ts
+++ b/frontend/src/constants/messages.ts
@@ -81,9 +81,6 @@ export const IMPORT_ASSIST_MESSAGES = {
RENDER_FAILED: "PDFの表示に失敗しました。別のファイルをお試しください。",
NO_TARGET: "先にフォームの入力欄をクリックして、流し込み先を選んでください。",
TAB_FALLBACK: "PDF",
- MAXIMIZE: "最大化",
- RESTORE: "元に戻す",
- MAXIMIZE_HINT: "ダブルクリックで最大化/元に戻す",
ZOOM_IN: "拡大",
ZOOM_OUT: "縮小",
} as const;
diff --git a/frontend/src/formTypes.ts b/frontend/src/formTypes.ts
index 0dc03374..661c0747 100644
--- a/frontend/src/formTypes.ts
+++ b/frontend/src/formTypes.ts
@@ -14,6 +14,4 @@ export type CareerProjectFieldKey =
| "end_date"
| "is_current"
| "role"
- | "challenge"
- | "action"
- | "result";
+ | "description";
diff --git a/frontend/src/hooks/career/usePdfPanelLayout.test.ts b/frontend/src/hooks/career/usePdfPanelLayout.test.ts
index 18198124..7a06a8c9 100644
--- a/frontend/src/hooks/career/usePdfPanelLayout.test.ts
+++ b/frontend/src/hooks/career/usePdfPanelLayout.test.ts
@@ -35,20 +35,13 @@ describe("usePdfPanelLayout", () => {
document.body.style.userSelect = "";
});
- it("初期幅を返し、toggleMaximize で最大化を反転する", () => {
+ it("初期幅を返す", () => {
const ref = makeContainerRef(1000, 900);
const { result } = renderHook(() =>
usePdfPanelLayout(ref, { initialWidth: 360, minWidth: 280, minFormWidth: 360 }),
);
expect(result.current.width).toBe(360);
- expect(result.current.maximized).toBe(false);
-
- act(() => result.current.toggleMaximize());
- expect(result.current.maximized).toBe(true);
-
- act(() => result.current.toggleMaximize());
- expect(result.current.maximized).toBe(false);
});
it("ドラッグで幅が変わり、最小/最大でクランプされる", () => {
diff --git a/frontend/src/hooks/career/usePdfPanelLayout.ts b/frontend/src/hooks/career/usePdfPanelLayout.ts
index 2011f2a6..77ba6be2 100644
--- a/frontend/src/hooks/career/usePdfPanelLayout.ts
+++ b/frontend/src/hooks/career/usePdfPanelLayout.ts
@@ -2,12 +2,10 @@ import { useCallback, useEffect, useRef, useState } from "react";
import type { MouseEvent as ReactMouseEvent, RefObject } from "react";
/**
- * PDF 取り込みパネルのレイアウト(左右リサイズ + 最大化)を管理するフック。
+ * PDF 取り込みパネルのレイアウト(左右リサイズ)を管理するフック。
*
* - リサイズ: フォームとパネルの間のスプリッターをドラッグして PDF カラム幅を変える。
* PDF は右カラムなので「幅 = コンテナ右端 - マウス X」。左にドラッグするほど広がる。
- * - 最大化: VSCode のタブをダブルクリックした時のように、PDF をフォームに覆い被せて全幅表示する
- * (maximized=true)。再度トグルで元の 2 カラムに戻す。
*
* 幅は最小 PDF 幅とフォームの最小確保幅でクランプする。
*/
@@ -29,14 +27,10 @@ type Options = {
};
export type UsePdfPanelLayoutReturn = {
- /** PDF カラムの現在幅(px)。最大化中は無視される。 */
+ /** PDF カラムの現在幅(px) */
width: number;
- /** 最大化(フォームに覆い被せて全幅表示)中か */
- maximized: boolean;
/** スプリッターの mousedown で呼ぶ。ドラッグ中の幅追従を開始する。 */
startResize: (e: ReactMouseEvent) => void;
- /** 最大化⇔元の 2 カラムをトグルする。 */
- toggleMaximize: () => void;
};
export function usePdfPanelLayout(
@@ -46,7 +40,6 @@ export function usePdfPanelLayout(
const { initialWidth = 360, minWidth = 280, minFormWidth = 360, reservedGap = 38 } = options;
const [width, setWidth] = useState(initialWidth);
- const [maximized, setMaximized] = useState(false);
const resizingRef = useRef(false);
// mousemove / mouseup は document 全体で拾う(スプリッター外へカーソルが出ても追従するため)。
@@ -81,7 +74,5 @@ export function usePdfPanelLayout(
document.body.style.cursor = "col-resize";
}, []);
- const toggleMaximize = useCallback(() => setMaximized((prev) => !prev), []);
-
- return { width, maximized, startResize, toggleMaximize };
+ return { width, startResize };
}
diff --git a/frontend/src/hooks/career/useProjectFormDirty.test.ts b/frontend/src/hooks/career/useProjectFormDirty.test.ts
index 49d43c63..ff688ae3 100644
--- a/frontend/src/hooks/career/useProjectFormDirty.test.ts
+++ b/frontend/src/hooks/career/useProjectFormDirty.test.ts
@@ -13,9 +13,7 @@ function buildProject(overrides: Partial
= {}): CareerProject
end_date: "2022-03",
is_current: false,
role: "Eng",
- challenge: "課題",
- action: "行動",
- result: "成果",
+ description: "課題・行動・成果",
team: { total: "5", members: [{ role: "PM", count: "1" }] },
technology_stacks: [{ category: "language", name: "TypeScript" }],
phases: ["要件定義", "開発"],
diff --git a/frontend/src/hooks/career/useProjectFormDirty.ts b/frontend/src/hooks/career/useProjectFormDirty.ts
index b0c88dbf..7c749778 100644
--- a/frontend/src/hooks/career/useProjectFormDirty.ts
+++ b/frontend/src/hooks/career/useProjectFormDirty.ts
@@ -15,9 +15,7 @@ export type ProjectFormDirty = {
end_date: boolean;
is_current: boolean;
role: boolean;
- challenge: boolean;
- action: boolean;
- result: boolean;
+ description: boolean;
};
/** 体制セクションの dirty(全体人数 or メンバー配列が変化) */
team: boolean;
@@ -47,9 +45,7 @@ export function useProjectFormDirty(
end_date: local.end_date !== base.end_date,
is_current: local.is_current !== base.is_current,
role: local.role !== base.role,
- challenge: local.challenge !== base.challenge,
- action: local.action !== base.action,
- result: local.result !== base.result,
+ description: local.description !== base.description,
};
const team = !isDeepEqual(local.team, base.team);
diff --git a/frontend/src/hooks/career/useProjectModalForm.test.ts b/frontend/src/hooks/career/useProjectModalForm.test.ts
index ab1fffad..3b67487e 100644
--- a/frontend/src/hooks/career/useProjectModalForm.test.ts
+++ b/frontend/src/hooks/career/useProjectModalForm.test.ts
@@ -10,9 +10,7 @@ const sampleProject: CareerProjectForm = {
end_date: "2024-12",
is_current: false,
role: "Backend",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "5", members: [{ role: "SE", count: "3" }] },
technology_stacks: [{ category: "language", name: "Python" }],
phases: ["設計"],
diff --git a/frontend/src/hooks/career/useProjectModalState.test.ts b/frontend/src/hooks/career/useProjectModalState.test.ts
index 2b848591..6d181e09 100644
--- a/frontend/src/hooks/career/useProjectModalState.test.ts
+++ b/frontend/src/hooks/career/useProjectModalState.test.ts
@@ -9,9 +9,7 @@ const dummyProject: CareerProjectForm = {
end_date: "2024-12",
is_current: false,
role: "エンジニア",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "", members: [] },
technology_stacks: [],
phases: [],
diff --git a/frontend/src/payloadBuilders.test.ts b/frontend/src/payloadBuilders.test.ts
index b60fd72f..d05991fa 100644
--- a/frontend/src/payloadBuilders.test.ts
+++ b/frontend/src/payloadBuilders.test.ts
@@ -17,9 +17,7 @@ const blankProject = (overrides: Partial = {}): CareerProject
end_date: "2024-06",
is_current: false,
role: "",
- challenge: "",
- action: "",
- result: "",
+ description: "",
team: { total: "", members: [] },
technology_stacks: [],
phases: [],
diff --git a/frontend/src/payloadBuilders.ts b/frontend/src/payloadBuilders.ts
index a3f8badf..b6c99fa1 100644
--- a/frontend/src/payloadBuilders.ts
+++ b/frontend/src/payloadBuilders.ts
@@ -21,9 +21,7 @@ export type CareerProjectForm = {
end_date: string;
is_current: boolean;
role: string;
- challenge: string;
- action: string;
- result: string;
+ description: string;
team: {
total: string;
members: TeamMemberForm[];
@@ -89,9 +87,7 @@ function buildProject(proj: CareerProjectForm): CareerProject {
end_date: proj.is_current ? "" : proj.end_date.trim(),
is_current: proj.is_current,
role: proj.role.trim(),
- challenge: proj.challenge.trim(),
- action: proj.action.trim(),
- result: proj.result.trim(),
+ description: proj.description.trim(),
team: buildTeam(proj.team),
technology_stacks: proj.technology_stacks
.map((stack) => ({
@@ -109,7 +105,7 @@ function buildClient(client: CareerClientForm): CareerClient {
has_client: client.has_client,
projects: client.projects
.map(buildProject)
- .filter((p) => hasAnyText([p.name, p.challenge, p.action, p.result])),
+ .filter((p) => hasAnyText([p.name, p.description])),
};
}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 6aea5446..93527f89 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -2,6 +2,10 @@
color-scheme: light;
font-family: "Noto Sans JP", "Hiragino Kaku Gothic ProN", sans-serif;
+ /* Layout(サイドバー幅・モバイル上部バー高。AuthenticatedLayout とモーダルのオフセットで共用) */
+ --sidebar-width: 220px;
+ --topbar-height: 48px;
+
/* Backgrounds */
--bg-page: #f9f6f1;
--bg-card: #ffffff;
@@ -40,6 +44,11 @@
--error: #a72323;
--success: #2e7d4f;
+ /* Warning (PDF スキャン警告などの注意喚起) */
+ --warning-text: #b45309;
+ --warning-bg: #fff7ed;
+ --warning-border: #fed7aa;
+
/* Shadow */
--shadow-card: 0 14px 30px rgba(80, 60, 30, 0.06);
@@ -101,6 +110,10 @@
--error: #f87171;
--success: #4ade80;
+ --warning-text: #fbbf24;
+ --warning-bg: #422006;
+ --warning-border: #854d0e;
+
--shadow-card: 0 14px 30px rgba(0, 0, 0, 0.3);
--header-btn-hover: rgba(255, 255, 255, 0.06);
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index f5774681..74b76f68 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -53,9 +53,8 @@ export type CareerProject = {
end_date: string;
is_current: boolean;
role: string;
- challenge: string;
- action: string;
- result: string;
+ /** 課題・行動・成果を統合した自由記述欄(見出し「詳細」) */
+ description: string;
team: ProjectTeam;
technology_stacks: CareerTechnologyStack[];
phases: string[];
diff --git a/frontend/tests/payloadBuilders.test.cjs b/frontend/tests/payloadBuilders.test.cjs
index a1c556de..5afbdf1d 100644
--- a/frontend/tests/payloadBuilders.test.cjs
+++ b/frontend/tests/payloadBuilders.test.cjs
@@ -28,9 +28,7 @@ test("buildCareerPayload trims data and keeps only non-empty technology stacks",
end_date: "2021-03",
is_current: false,
role: " メンバー ",
- challenge: " 課題テスト ",
- action: " 行動テスト ",
- result: " パフォーマンス改善 ",
+ description: " 課題・行動・成果テスト ",
team: {
total: " 5 ",
members: [
@@ -79,6 +77,10 @@ test("buildCareerPayload trims data and keeps only non-empty technology stacks",
assert.equal(payload.experiences[0].clients[0].projects.length, 1);
assert.equal(payload.experiences[0].clients[0].projects[0].name, "プロジェクトA");
assert.equal(payload.experiences[0].clients[0].projects[0].role, "メンバー");
+ assert.equal(
+ payload.experiences[0].clients[0].projects[0].description,
+ "課題・行動・成果テスト"
+ );
assert.deepEqual(payload.experiences[0].clients[0].projects[0].technology_stacks, [
{
category: "framework",