From 66fa027837b271639dbf9f35a313a2f26616e7e3 Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Mon, 1 Jun 2026 19:21:21 +0900 Subject: [PATCH 1/5] resume PDF space fix --- backend/app/services/pdf/templates/resume.css | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/backend/app/services/pdf/templates/resume.css b/backend/app/services/pdf/templates/resume.css index 819c7455..8feb0a53 100644 --- a/backend/app/services/pdf/templates/resume.css +++ b/backend/app/services/pdf/templates/resume.css @@ -39,13 +39,17 @@ h2 { border-bottom: 2px solid #333; padding-bottom: 1mm; margin: 6mm 0 3mm 0; + /* 見出しがページ末尾に孤立して直後の本文だけ次ページに行くのを防ぐ */ + page-break-after: avoid; } /* 会社ブロック */ .company { border: 0.5px solid #666; margin-bottom: 3mm; - page-break-inside: avoid; + /* 会社ブロック全体を 1 ページに固めると、収まらない時に丸ごと次ページへ送られ、 + 直前(職務要約など)に大きな空白が残る。ページ跨ぎを許可してページを詰める。 + 代わりに分割を避けるのは下位の .project 単位で行う。 */ } .company-header { @@ -54,6 +58,8 @@ h2 { font-size: 10pt; font-weight: bold; border-bottom: 0.3px solid #999; + /* ヘッダがページ末尾に孤立し本文だけ次ページに分かれるのを防ぐ */ + page-break-after: avoid; } .company-info { @@ -104,11 +110,16 @@ h2 { margin: 3mm 0 2mm 0; padding-bottom: 1mm; border-bottom: 2px solid #333; + /* 取引先名見出しの孤立防止 */ + page-break-after: avoid; } /* プロジェクトテーブル */ .project { margin-bottom: 2mm; + /* 1 案件(ヘッダ+テーブル)は途中で割らず可読性を保つ。 + 会社ブロックはページ跨ぎ可、分割回避はこの単位で行う。 */ + page-break-inside: avoid; } .project-header { From d4dd29108cf0e63a1799d8fd5ee3bace67f88dfe Mon Sep 17 00:00:00 2001 From: Wada Yusuke Date: Mon, 1 Jun 2026 22:50:53 +0900 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=E8=81=B7=E5=8B=99=E7=B5=8C=E6=AD=B4?= =?UTF-8?q?=E6=9B=B8PDF=E3=81=AE=E4=BD=99=E7=99=BD=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=81=A8=E3=83=95=E3=82=A9=E3=83=BC=E3=83=A0=E3=83=BB=E5=87=BA?= =?UTF-8?q?=E5=8A=9B=E8=A1=A8=E8=A8=98=E3=81=AE=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PDF: 1ページ目の余分な余白を解消(.project の page-break-inside 削除、 body margin:0)。表がページ分割される際は thead で列見出しを繰り返す - PDF: プロジェクト表の体制カラムを廃止し、役割行へ 「役割:X、体制:6名(PM:1名、SE:6名)」形式で併記。空いた幅を業務内容へ - PDF/Markdown: 出力表記を統一(開発環境/技術スタック→スキルセット、 取引先名→案件名) - フォーム: IT企業チェックを会社名・事業内容と同一行・入力欄と横並びに配置 (幅比 会社名:事業内容:IT企業 = 4.5:5:0.5) - フォーム: 資格入力の幅比を 資格名:取得日 = 7:3 に調整 - フォーム: スキル入力ラベルを「技術スタック」→「スキルセット」に統一 Co-Authored-By: Claude Opus 4.8 --- .../markdown/generators/resume_generator.py | 2 +- .../pdf/generators/resume_generator.py | 52 +++++++++++-------- backend/app/services/pdf/templates/resume.css | 17 +++--- .../CareerExperienceEditor.tsx | 38 ++++++++------ .../forms/CareerFormEditors/ClientEditor.tsx | 2 +- .../forms/CareerResumeForm.module.css | 14 +++++ .../src/components/forms/ProjectModal.tsx | 6 +-- .../sections/CareerQualificationsSection.tsx | 3 +- 8 files changed, 81 insertions(+), 53 deletions(-) diff --git a/backend/app/services/markdown/generators/resume_generator.py b/backend/app/services/markdown/generators/resume_generator.py index e9f35c58..131b5f4f 100644 --- a/backend/app/services/markdown/generators/resume_generator.py +++ b/backend/app/services/markdown/generators/resume_generator.py @@ -144,7 +144,7 @@ def build_resume_markdown(payload: dict[str, Any]) -> str: f"{CATEGORY_LABELS.get(c, c)}: {', '.join(ns)}" for c, ns in grouped.items() ] - lines.append(field_line("技術スタック", " / ".join(parts))) + lines.append(field_line("スキルセット", " / ".join(parts))) lines.append("") self_pr = payload.get("self_pr", "") diff --git a/backend/app/services/pdf/generators/resume_generator.py b/backend/app/services/pdf/generators/resume_generator.py index 56ad0c5e..2b62093f 100644 --- a/backend/app/services/pdf/generators/resume_generator.py +++ b/backend/app/services/pdf/generators/resume_generator.py @@ -54,6 +54,24 @@ def _format_periods(periods: list) -> str: return "、".join(parts) +def _format_team(project) -> str: + """体制の人数情報を「6名(PM:1名、SE:6名)」形式の1行文字列にする。 + + 後方互換(旧 scale → team)の正規化は shared に集約。情報が無ければ空文字を返す。 + """ + team = normalize_team(project) + if not team: + return "" + total = _a(team, "total") + total_text = f"{_esc(total)}名" if total else "" + members = _a(team, "members", []) + member_strs = [ + f"{_esc(_a(m, 'role'))}:{_esc(_a(m, 'count', 0))}名" for m in members if _a(m, "role") + ] + member_text = f"({'、'.join(member_strs)})" if member_strs else "" + return f"{total_text}{member_text}" + + def _build_project_html(project) -> str: """プロジェクト1件分のHTMLを組み立てる""" # ヘッダー(3行構成: 期間/プロジェクト名、役割、工程) @@ -71,8 +89,11 @@ def _build_project_html(project) -> str: line1_parts.append(_esc(name)) line1 = " / ".join(line1_parts) if line1_parts else "" - # 2行目: 役割 - line2 = f"役割:{_esc(role)}" if role else "" + # 2行目: 役割(体制の人数情報を「、体制:…」として役割の右側に併記する) + team_text = _format_team(project) + role_part = f"役割:{_esc(role)}" if role else "" + team_part = f"体制:{team_text}" if team_text else "" + line2 = "、".join(p for p in [role_part, team_part] if p) # 3行目: 工程 line3 = "" @@ -91,7 +112,7 @@ def _build_project_html(project) -> str: left_parts.append(_md(description)) left_content = "".join(left_parts) if left_parts else "-" - # 右カラム: 開発環境(技術スタック) + # 右カラム: スキルセット(技術スタックのカテゴリ別表示) stacks = _a(project, "technology_stacks", []) grouped = group_stacks_by_category(stacks) right_parts: list[str] = [] @@ -102,28 +123,13 @@ def _build_project_html(project) -> str: ) right_content = "
".join(right_parts) if right_parts else "-" - # 体制(後方互換: 旧 scale → team の正規化は shared に集約) - team = normalize_team(project) - team_parts: list[str] = [] - if team: - total = _a(team, "total") - if total: - team_parts.append(f"{_esc(total)}名") - members = _a(team, "members", []) - member_strs = [ - f"{_esc(_a(m, 'role'))}:{_a(m, 'count', 0)}" for m in members if _a(m, "role") - ] - if member_strs: - team_parts.append(" / ".join(member_strs)) - team_text = "
".join(team_parts) if team_parts else "-" - + # 体制は表のカラムではなく役割行に併記する(line2 で処理済み)。 return ( f'
{header_html}' f'' - f"" - f'' - f'' - f'' + f"" + f'' + f'' f"
業務内容開発環境体制
{left_content}{right_content}{team_text}
業務内容スキルセット
{left_content}{right_content}
" ) @@ -223,7 +229,7 @@ def _build_html(resume: dict) -> str: client_name = _a(client, "name") if client_name: parts.append( - f'
' f"取引先名:{_esc(client_name)}
", + f'
' f"案件名:{_esc(client_name)}
", ) projects = _a(client, "projects", []) for proj in projects: diff --git a/backend/app/services/pdf/templates/resume.css b/backend/app/services/pdf/templates/resume.css index 8feb0a53..e868d469 100644 --- a/backend/app/services/pdf/templates/resume.css +++ b/backend/app/services/pdf/templates/resume.css @@ -14,6 +14,7 @@ } body { + margin: 0; font-family: "NotoSansJP", sans-serif; font-size: 9pt; line-height: 1.6; @@ -110,16 +111,17 @@ h2 { margin: 3mm 0 2mm 0; padding-bottom: 1mm; border-bottom: 2px solid #333; - /* 取引先名見出しの孤立防止 */ + /* 案件名見出しの孤立防止 */ page-break-after: avoid; } /* プロジェクトテーブル */ .project { margin-bottom: 2mm; - /* 1 案件(ヘッダ+テーブル)は途中で割らず可読性を保つ。 - 会社ブロックはページ跨ぎ可、分割回避はこの単位で行う。 */ - page-break-inside: avoid; + /* 案件が残りスペースに収まらない時に丸ごと次ページへ送られると、 + 枠付き company 内に大きな余白が残る。ページを詰めるため分割を許可する + (長い案件はページ境界で割れるが、余白を残すより詰めることを優先)。 + 分割時の列見出しは project-table の thead で繰り返す。 */ } .project-header { @@ -150,17 +152,14 @@ h2 { } .project-table td.desc { - width: 60%; + /* 体制カラムを廃止した分を業務内容へ割り当てる */ + width: 75%; } .project-table td.env { width: 25%; } -.project-table td.team { - width: 15%; -} - /* 業務内容カラム内のラベル */ .project-table td.desc p { margin: 0 0 0.5em 0; diff --git a/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx b/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx index 79e68cba..a56be292 100644 --- a/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx +++ b/frontend/src/components/forms/CareerFormEditors/CareerExperienceEditor.tsx @@ -85,7 +85,8 @@ export function CareerExperienceEditor({ } > -
+ {/* 会社名:事業内容:IT企業 = 4.5:5:0.5 の幅比で配置 */} +
+ {/* IT企業かどうか(非ITは取引先を持たず詳細のみ)。会社名・事業内容と同じ行に配置。 + 上段は labelText のスペーサで隣のラベル行と高さを合わせ、下段のトグルを入力欄と横並びにする。 */} +
@@ -215,19 +236,6 @@ export function CareerExperienceEditor({
- {/* IT企業かどうか(非ITは取引先を持たず詳細のみ) */} - - {!exp.is_it_company && (