{% extends 'default_frame.twig' %}
{% block main %}
<style>
/* フォント強制読み込み */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500;700&display=swap');
* {
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", "Segoe UI", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, "Noto Sans JP", sans-serif !important;
}
/* 見積もりシミュレーター専用スタイル */
.nz-estimate-wrapper {
max-width: 1200px;
margin: 60px auto;
padding: 0 20px;
}
.nz-estimate-card {
background: #ffffff;
border-radius: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.nz-estimate-header {
background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
padding: 60px 40px;
text-align: center;
}
.nz-estimate-title {
font-size: 42px;
font-weight: 800;
color: #ffffff;
margin: 0 0 16px 0;
letter-spacing: -0.5px;
}
.nz-estimate-description {
font-size: 18px;
color: rgba(255, 255, 255, 0.95);
line-height: 1.7;
}
.nz-estimate-description img {
max-width: 100%;
height: auto;
display: block;
margin: 16px auto;
border-radius: 8px;
}
.nz-estimate-description p {
margin: 8px 0;
}
.nz-estimate-description br {
display: block;
content: "";
margin: 4px 0;
}
.nz-estimate-body {
padding: 50px 40px;
}
/* カテゴリセクション */
.category-section {
margin-bottom: 20px;
overflow: hidden;
}
.category-row {
display: flex;
align-items: center;
gap: 20px;
width: 100%;
max-width: 100%;
}
.category-section.child-category {
margin-left: 40px;
padding-left: 20px;
border-left: 3px solid #cbd5e0;
display: none;
}
.category-section.child-category.active {
display: block;
animation: slideDown 0.3s ease;
}
@keyframes slideDown {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.category-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 0;
}
.category-title {
font-size: 20px;
font-weight: 700;
color: #2d3748;
margin: 0;
min-width: 150px;
flex-shrink: 0;
}
.category-title.child {
font-size: 18px;
color: #4a5568;
}
.category-required-badge {
display: inline-block;
background: #e53e3e;
color: white;
font-size: 12px;
font-weight: 700;
padding: 4px 12px;
border-radius: 12px;
}
.category-optional-badge {
display: inline-block;
background: #718096;
color: white;
font-size: 12px;
font-weight: 700;
padding: 4px 12px;
border-radius: 12px;
}
.parts-select {
flex: 1;
width: 100%;
max-width: 100%;
padding: 12px 16px;
font-size: 16px;
color: #2d3748 !important;
background-color: #f7fafc !important;
border: 2px solid #e2e8f0 !important;
border-radius: 12px !important;
transition: all 0.3s ease !important;
cursor: pointer;
box-sizing: border-box;
}
.parts-select:focus {
outline: none !important;
border-color: #667eea !important;
background-color: #ffffff !important;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
}
.parts-select option {
padding: 10px;
}
.parts-select option[value=""] {
color: #a0aec0 !important;
font-style: italic;
}
.parts-select option:disabled {
color: #a0aec0 !important;
}
/* 見積もりサマリー(常時表示) */
.estimate-summary {
background: linear-gradient(135deg, #f7fafc 0%, #edf2f7 100%);
border-radius: 16px;
padding: 30px;
margin-top: 40px;
}
.summary-header {
margin-bottom: 20px;
padding-bottom: 15px;
}
.summary-note {
font-size: 14px;
color: #1a202c;
font-weight: 600;
text-align: center;
}
.summary-items {
margin-bottom: 20px;
min-height: 100px;
}
.summary-empty {
text-align: center;
padding: 40px 20px;
color: #a0aec0;
font-size: 15px;
}
.summary-item {
display: block;
padding: 12px 0;
border-bottom: 1px solid #e2e8f0;
}
.summary-item-left {
width: 100%;
}
.summary-item-category {
font-size: 13px;
color: #718096;
margin-bottom: 4px;
}
.summary-item-name {
font-size: 15px;
font-weight: 600;
color: #2d3748;
}
.summary-total {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24px 28px;
margin-top: 20px;
border-top: none;
background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(168, 179, 255, 0.3);
}
.summary-total-label {
font-size: 22px;
font-weight: 700;
color: #ffffff;
}
.summary-total-price {
font-size: 36px;
font-weight: 800;
color: #ffffff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.summary-total-price.hidden {
color: rgba(255, 255, 255, 0.4);
text-shadow: none;
}
/* 必須項目未選択警告 */
.required-warning {
background: #fff5f5;
border: 2px solid #fc8181;
border-radius: 12px;
padding: 15px 20px;
margin-top: 20px;
text-align: center;
display: none;
}
.required-warning.active {
display: block;
}
.required-warning-text {
font-size: 14px;
font-weight: 600;
color: #742a2a;
}
/* お客様情報フォーム */
.customer-form {
margin-top: 30px;
padding: 25px;
background: #f7fafc;
border-radius: 12px;
border: 1px solid #e2e8f0;
display: none;
}
.customer-form.active {
display: block;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.customer-form h3 {
font-size: 20px;
font-weight: 700;
color: #2d3748;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 25px;
}
.form-row {
display: flex;
gap: 20px;
margin-bottom: 0;
}
.form-group-half {
flex: 1;
margin-bottom: 25px;
}
.form-label {
display: block;
font-size: 16px;
font-weight: 700;
color: #2d3748;
margin-bottom: 10px;
}
.form-label .required {
color: #e53e3e;
margin-left: 4px;
}
.form-control {
width: 100%;
padding: 14px 18px;
font-size: 16px;
color: #2d3748 !important;
background-color: #f7fafc !important;
border: 2px solid #e2e8f0 !important;
border-radius: 12px !important;
transition: all 0.3s ease !important;
}
.form-control:focus {
outline: none !important;
border-color: #667eea !important;
background-color: #ffffff !important;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1) !important;
}
textarea.form-control {
min-height: 120px;
resize: vertical;
}
/* 送信ボタン */
.submit-section {
margin-top: 40px;
text-align: center;
display: none;
}
.submit-section.active {
display: block;
animation: fadeIn 0.3s ease;
}
.submit-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #ffffff;
font-size: 18px;
font-weight: 700;
padding: 18px 60px;
border: none;
border-radius: 50px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.4);
}
.submit-btn:hover {
transform: translateY(-2px);
box-shadow: 0 12px 32px rgba(102, 126, 234, 0.5);
}
.submit-btn:disabled {
background: #cbd5e0;
cursor: not-allowed;
box-shadow: none;
}
/* ローディング */
.loading {
text-align: center;
padding: 40px;
display: none;
}
.loading.active {
display: block;
}
.loading-spinner {
border: 4px solid #e2e8f0;
border-top: 4px solid #667eea;
border-radius: 50%;
width: 50px;
height: 50px;
animation: spin 1s linear infinite;
margin: 0 auto 20px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 完了メッセージ */
.complete-message {
text-align: center;
padding: 60px 40px;
display: none;
}
.complete-message.active {
display: block;
animation: fadeIn 0.5s ease;
}
.complete-icon {
font-size: 72px;
color: #48bb78;
margin-bottom: 20px;
}
.complete-title {
font-size: 28px;
font-weight: 700;
color: #2d3748;
margin-bottom: 15px;
}
.complete-text {
font-size: 16px;
color: #718096;
line-height: 1.8;
}
/* 注意事項 */
.notice-text {
margin-top: 20px;
padding: 15px 20px;
background-color: #fffbeb;
border-left: 4px solid #f59e0b;
border-radius: 4px;
color: #92400e;
font-size: 14px;
line-height: 1.7;
}
/* フリーエリア */
.nz-free-area {
max-width: 1200px;
margin: 40px auto;
padding: 0 20px;
text-align: center;
}
.nz-free-area h2 {
font-size: 28px;
font-weight: 700;
color: #2d3748;
margin-bottom: 20px;
text-align: center;
}
.nz-free-area h3 {
font-size: 22px;
font-weight: 600;
color: #4a5568;
margin: 30px 0 15px;
text-align: left;
}
.nz-free-area p {
font-size: 16px;
color: #718096;
line-height: 1.8;
margin-bottom: 15px;
text-align: left;
}
.nz-free-area img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 20px 0;
}
.nz-free-area .btn {
display: inline-block;
width: 100%;
max-width: 400px;
padding: 18px 30px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white !important;
text-decoration: none;
border-radius: 16px;
font-weight: 700;
font-size: 18px;
text-align: center;
transition: all 0.3s ease;
border: none;
cursor: pointer;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
}
.nz-free-area .btn:hover {
background: linear-gradient(135deg, #059669 0%, #047857 100%);
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.4);
}
/* 購入セクション */
.purchase-section {
margin-bottom: 40px;
}
.purchase-card {
background: #ffffff;
border: none;
border-radius: 0;
padding: 20px 0;
text-align: center;
}
.purchase-btn {
width: 100%;
max-width: 400px;
padding: 18px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 18px;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.3);
margin-bottom: 15px;
}
.purchase-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
}
.purchase-btn:active {
transform: translateY(0);
}
.purchase-notice {
margin-top: 15px;
padding: 15px;
background-color: #f0fdf4;
border-radius: 8px;
color: #065f46;
font-size: 14px;
line-height: 1.7;
}
/* スマホ用固定サマリー:PC/タブレットでは非表示 */
.estimate-summary-sticky {
display: none;
}
/* ==================== レスポンシブ対応 ==================== */
@media (max-width: 768px) {
/* コンテナ */
.estimate-container {
padding: 10px;
}
/* ヘッダー */
.estimate-header {
padding: 20px 10px;
margin-bottom: 15px;
}
.estimate-header h1 {
font-size: 20px;
}
.estimate-header p {
font-size: 13px;
}
/* カテゴリセクション */
.category-section {
padding: 15px;
margin-bottom: 12px;
width: 100%;
box-sizing: border-box;
}
.category-row {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.category-header {
flex-direction: row;
align-items: center;
gap: 10px;
margin-bottom: 0;
width: 100%;
}
.category-name {
font-size: 16px;
}
.required-badge {
font-size: 11px;
padding: 3px 8px;
}
/* セレクトボックス */
.parts-select {
font-size: 15px;
padding: 12px 35px 12px 12px;
margin-top: 0;
width: 100% !important;
max-width: 100% !important;
flex: none;
}
/* 見積もりサマリー */
.estimate-summary {
padding: 15px;
margin-bottom: 15px;
}
.summary-note {
font-size: 12px;
}
.summary-total {
flex-direction: row;
align-items: center;
gap: 15px;
padding: 12px 0;
}
.summary-total-label {
font-size: 15px;
}
.summary-total-price {
font-size: 24px;
text-align: right;
}
/* 購入セクション */
.purchase-section {
margin-bottom: 15px;
}
.purchase-card {
padding: 15px;
}
.purchase-btn {
max-width: 100%;
font-size: 15px;
padding: 14px;
}
.purchase-notice {
font-size: 12px;
padding: 10px;
margin-top: 10px;
}
/* お客様情報フォーム */
.customer-form {
padding: 15px;
margin-top: 15px;
}
.customer-form h3 {
font-size: 16px;
margin-bottom: 12px;
}
.form-row {
flex-direction: column;
gap: 0;
}
.form-group {
margin-bottom: 15px;
}
.form-label {
font-size: 14px;
margin-bottom: 6px;
}
.form-control {
padding: 10px 12px;
font-size: 15px;
}
textarea.form-control {
min-height: 80px;
}
/* 送信ボタン */
.submit-section {
padding: 15px;
margin-top: 15px;
}
.submit-btn {
padding: 14px;
font-size: 15px;
}
/* 必須警告 */
.required-warning {
padding: 10px;
font-size: 12px;
margin: 10px 0;
}
/* フリーエリア */
.nz-free-area {
padding: 20px 15px;
font-size: 14px;
}
.nz-free-area h2 {
font-size: 20px;
}
.nz-free-area h3 {
font-size: 18px;
}
}
@media (max-width: 480px) {
/* さらに小さい画面用 */
.estimate-container {
padding: 8px;
}
.estimate-header {
padding: 15px 8px;
}
.estimate-header h1 {
font-size: 18px;
}
.estimate-header p {
font-size: 12px;
}
.category-section {
padding: 12px;
margin-bottom: 10px;
}
.category-row {
display: flex;
flex-direction: column;
gap: 8px;
}
.category-name {
font-size: 15px;
}
.parts-select {
font-size: 14px;
padding: 10px 30px 10px 10px;
width: 100% !important;
max-width: 100% !important;
flex: none;
}
.estimate-summary {
padding: 12px;
}
.summary-title {
font-size: 15px;
}
.summary-total {
padding: 16px 20px !important;
border-radius: 12px;
}
.summary-total-label {
font-size: 18px !important;
}
.summary-total-price {
font-size: 26px !important;
}
/* スマホ用:下部固定サマリー(クローン) */
.estimate-summary-sticky {
display: none; /* 初期状態では非表示 */
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: linear-gradient(135deg, #a8b3ff 0%, #d4a5ff 100%);
box-shadow: 0 -4px 12px rgba(168, 179, 255, 0.4);
z-index: 9999;
padding: 8px 16px;
}
.estimate-summary-sticky.visible {
display: block !important; /* スクロールで表示 */
}
.estimate-summary-sticky .summary-total {
margin: 0;
padding: 0;
border-top: none;
display: flex;
justify-content: space-between;
align-items: center;
background: transparent;
box-shadow: none;
border-radius: 0;
}
.estimate-summary-sticky .summary-total-label {
font-size: 14px;
font-weight: 700;
color: #ffffff;
}
.estimate-summary-sticky .summary-total-price {
font-size: 20px;
font-weight: 800;
color: #ffffff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* コンテンツが固定サマリーに隠れないよう余白 */
body.sticky-summary-active {
padding-bottom: 50px;
}
.purchase-card {
padding: 12px;
}
.purchase-btn {
font-size: 14px;
padding: 12px;
}
.customer-form {
padding: 12px;
}
.form-group {
margin-bottom: 12px;
}
.submit-section {
padding: 12px;
}
}
</style>
<div class="nz-estimate-wrapper">
<div class="nz-estimate-card">
<div class="nz-estimate-header">
<h1 class="nz-estimate-title">
{% if config.pageTitle %}
{{ config.pageTitle|replace({'<': '<', '>': '>', '"': '"', ''': "'", '&': '&'})|raw }}
{% else %}
見積もりシミュレーター
{% endif %}
</h1>
<div class="nz-estimate-description">
{% if config.pageDescription %}
{{ config.pageDescription|replace({'<': '<', '>': '>', '"': '"', ''': "'", '&': '&'})|raw }}
{% else %}
パーツを選択して、お見積もりを作成いただけます。
{% endif %}
</div>
</div>
<div class="nz-estimate-body">
{% for item in categoriesWithParts %}
<div class="category-section" data-category-id="{{ item.category.id }}">
<div class="category-row">
<div class="category-header">
<h2 class="category-title">{{ item.category.name }}</h2>
{% if item.category.isRequired %}
<span class="category-required-badge">必須</span>
{% else %}
<span class="category-optional-badge">任意</span>
{% endif %}
</div>
{# パーツを選択 #}
<select class="parts-select"
data-category-id="{{ item.category.id }}"
data-required="{{ item.category.isRequired ? '1' : '0' }}"
data-category-name="{{ item.category.name }}">
<option value="">選択してください</option>
{% for parts in item.parts %}
<option value="{{ parts.id }}"
data-price="{{ parts.price }}"
data-name="{{ parts.productName }}"
data-child-category-id="{{ parts.childCategoryId ? parts.childCategoryId : '' }}"
data-child-filter-keywords="{{ parts.childFilterKeywords ? parts.childFilterKeywords : '' }}">
{{ parts.productName }}
</option>
{% endfor %}
</select>
</div>
</div>
{% endfor %}
{# 全パーツデータをJavaScript用に埋め込み #}
<script type="application/json" id="allPartsData">
[
{% for item in categoriesWithParts %}
{% for parts in item.parts %}
{
"id": {{ parts.id }},
"category_id": {{ item.category.id }},
"category_name": {{ item.category.name|json_encode|raw }},
"name": {{ parts.productName|json_encode|raw }},
"model_number": {{ parts.modelNumber|default('')|json_encode|raw }},
"price": {{ parts.price }},
"child_category_id": {{ parts.childCategoryId ? parts.childCategoryId|json_encode|raw : 'null' }},
"child_filter_keywords": {{ parts.childFilterKeywords ? parts.childFilterKeywords|json_encode|raw : 'null' }}
}{% if not loop.parent.loop.last or not loop.last %},{% endif %}
{% endfor %}
{% endfor %}
]
</script>
{# カテゴリデータをJavaScriptに埋め込み(子カテゴリを含むすべて) #}
<script type="application/json" id="allCategoriesData">
[
{% for item in allCategoriesWithParts %}
{
"id": {{ item.category.id }},
"name": {{ item.category.name|json_encode|raw }},
"is_required": {{ item.category.isRequired ? 'true' : 'false' }},
"parts": [
{% for parts in item.parts %}
{
"id": {{ parts.id }},
"name": {{ parts.productName|json_encode|raw }},
"model_number": {{ parts.modelNumber|default('')|json_encode|raw }},
"price": {{ parts.price }},
"child_category_id": {{ parts.childCategoryId ? parts.childCategoryId|json_encode|raw : 'null' }},
"child_filter_keywords": {{ parts.childFilterKeywords ? parts.childFilterKeywords|json_encode|raw : 'null' }}
}{% if not loop.last %},{% endif %}
{% endfor %}
]
}{% if not loop.last %},{% endif %}
{% endfor %}
]
</script>
<!-- 見積もりサマリー(常時表示) -->
<div class="estimate-summary">
<div class="summary-header">
<div class="summary-note">※ 必須項目をすべて選択すると金額が表示されます</div>
</div>
<!-- 必須項目未選択警告 -->
<div class="required-warning" id="requiredWarning">
<div class="required-warning-text">
⚠️ 必須カテゴリをすべて選択してください
</div>
</div>
<div class="summary-total">
<div class="summary-total-label">合計金額</div>
<div class="summary-total-price" id="totalPriceArea">
¥<span id="totalPrice">---</span>
</div>
</div>
</div>
<!-- スマホ用:下部固定サマリー -->
<div class="estimate-summary-sticky" id="stickySummary">
<div class="summary-total">
<div class="summary-total-label">合計金額</div>
<div class="summary-total-price">
¥<span id="stickyTotalPrice">---</span>
</div>
</div>
</div>
<!-- 今すぐ購入セクション -->
<div class="purchase-section" id="purchaseSection" style="display: none;">
<div class="purchase-card">
<button type="button" class="purchase-btn" id="purchaseBtn">
🛒 すぐに購入
</button>
{% if config.purchaseNoticeText %}
<div class="purchase-notice">
{{ config.purchaseNoticeText|nl2br }}
</div>
{% endif %}
</div>
</div>
<!-- お客様情報フォーム -->
<div class="customer-form" id="customerForm">
<h3>お客様情報</h3>
<form id="estimateForm">
<div class="form-row">
<div class="form-group form-group-half">
<label class="form-label">
お名前<span class="required">*</span>
</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="form-group form-group-half">
<label class="form-label">
電話番号<span class="required">*</span>
</label>
<input type="tel" name="tel" class="form-control" required>
</div>
</div>
<div class="form-group">
<label class="form-label">
メールアドレス<span class="required">*</span>
</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="form-group">
<label class="form-label">ご要望・備考</label>
<textarea name="message" class="form-control" rows="3"></textarea>
</div>
</form>
</div>
<!-- 送信ボタン -->
<div class="submit-section" id="submitSection">
<button type="button" class="submit-btn" id="submitBtn">
見積もり依頼を送信
</button>
{% if config.noticeText %}
<div class="notice-text">
{{ config.noticeText|nl2br }}
</div>
{% endif %}
</div>
<!-- ローディング -->
<div class="loading" id="loading">
<div class="loading-spinner"></div>
<p>送信中...</p>
</div>
<!-- 完了メッセージ -->
<div class="complete-message" id="completeMessage">
<div class="complete-icon">✓</div>
<div class="complete-title">送信完了</div>
<div class="complete-text">
お見積もり依頼を受け付けました。<br>
担当者より折り返しご連絡いたします。
</div>
</div>
</div>
</div>
</div>
<!-- フリーエリア -->
{% if config.freeAreaHtml %}
<div class="nz-free-area">
{{ config.freeAreaHtml|raw }}
</div>
{% endif %}
<script>
$(function() {
let selectedParts = [];
// 内部料金設定(顧客には非表示)
const additionalCharge = {{ config.additionalCharge|default(0) }};
const discountRate = {{ config.discountRate|default(0) }};
// データ読み込み
const allParts = JSON.parse($('#allPartsData').text());
const allCategories = JSON.parse($('#allCategoriesData').text());
// 必須カテゴリを取得
const requiredCategories = [];
allCategories.forEach(function(cat) {
if (cat.is_required) {
requiredCategories.push(cat.id);
}
});
// パーツ選択時の処理
$(document).on('change', '.parts-select', function() {
const $select = $(this);
const partsId = $select.val();
const categoryId = $select.data('category-id');
const $currentSection = $select.closest('.category-section');
// 現在のセクションの後続の子カテゴリを削除(アニメーション付き)
$currentSection.nextAll('.category-section.child-category').each(function() {
const $childSection = $(this);
const parentId = $childSection.data('parent-id');
// 現在のカテゴリの子カテゴリのみ削除
if (parentId == categoryId) {
$childSection.removeClass('active').fadeOut(200, function() {
$(this).remove();
});
}
});
if (!partsId) {
updateSummary();
return;
}
// 選択したパーツ情報を取得
const parts = allParts.find(p => p.id == partsId);
if (!parts) {
updateSummary();
return;
}
// 子カテゴリがあるか確認(カンマ区切りで複数対応)
if (parts.child_category_id) {
const childCategoryIds = parts.child_category_id.toString().split(',').map(id => parseInt(id.trim())).filter(id => id > 0);
console.log('========================================');
console.log('選択されたパーツ:', parts.name);
console.log('子カテゴリID:', childCategoryIds);
console.log('フィルタキーワード:', parts.child_filter_keywords);
childCategoryIds.forEach(function(childCatId) {
const childCategory = allCategories.find(c => c.id == childCatId);
console.log('--- 子カテゴリID:', childCatId, '---');
console.log('子カテゴリ名:', childCategory ? childCategory.name : 'NOT FOUND');
if (childCategory && childCategory.parts.length > 0) {
let filteredParts = childCategory.parts;
// フィルタキーワードがある場合は絞り込み
if (parts.child_filter_keywords) {
const keywords = parts.child_filter_keywords.split(',').map(k => k.trim().toLowerCase());
console.log('=== フィルタ実行 ===');
console.log('子カテゴリ:', childCategory.name);
console.log('フィルタキーワード:', keywords);
console.log('全パーツ数:', childCategory.parts.length);
filteredParts = childCategory.parts.filter(p => {
const searchText = (p.name + ' ' + (p.model_number || '')).toLowerCase();
const matched = keywords.some(kw => searchText.includes(kw));
console.log(` - ${p.name}: ${matched ? 'マッチ' : '除外'} (検索文字: ${searchText})`);
return matched;
});
console.log('フィルタ後パーツ数:', filteredParts.length);
}
// フィルタ後のパーツがある場合のみ子カテゴリを表示
if (filteredParts.length > 0) {
// 子カテゴリセクションを作成
createChildCategorySection({
...childCategory,
parts: filteredParts
}, categoryId);
}
}
});
}
updateSummary();
});
// 子カテゴリセクション作成(セレクトボックス形式)
function createChildCategorySection(category, parentCategoryId) {
const $parentSection = $(`.category-section[data-category-id="${parentCategoryId}"]`);
const childHtml = `
<div class="category-section child-category" data-category-id="${category.id}" data-parent-id="${parentCategoryId}">
<div class="category-row">
<div class="category-header">
<h2 class="category-title child">└ ${category.name}</h2>
${category.is_required ? '<span class="category-required-badge">必須</span>' : '<span class="category-optional-badge">任意</span>'}
</div>
<select class="parts-select"
data-category-id="${category.id}"
data-parent-category-id="${parentCategoryId}"
data-required="${category.is_required ? '1' : '0'}"
data-category-name="${category.name}">
${category.parts.map((p, index) => `
<option value="${p.id}"
data-price="${p.price}"
data-name="${p.name}"
data-child-category-id="${p.child_category_id || ''}"
data-child-filter-keywords="${p.child_filter_keywords || ''}"
${index === 0 ? 'selected' : ''}>
${p.name}
</option>
`).join('')}
</select>
</div>
</div>
`;
$parentSection.after(childHtml);
// 挿入後、activeクラスを追加してスライド表示
setTimeout(function() {
$(`.child-category[data-parent-id="${parentCategoryId}"]`).addClass('active');
}, 10);
}
// サマリー更新
function updateSummary() {
const items = [];
const selectedCategories = new Set();
let total = 0;
// すべてのセレクトボックスを取得
$('.parts-select').each(function() {
const $select = $(this);
const value = $select.val();
if (value && value !== '') {
const selectedOption = $select.find('option:selected');
const categoryId = $select.data('category-id');
const parentCategoryId = $select.data('parent-category-id');
const categoryName = $select.data('category-name');
const price = Math.floor(parseFloat(selectedOption.data('price'))); // 小数点以下切り捨て
const name = selectedOption.data('name');
items.push({
parts_id: value,
category_name: categoryName,
name: name,
price: price
});
// 必須カテゴリの判定: 子カテゴリの場合は親カテゴリIDを、そうでない場合は自身のIDを使用
const checkCategoryId = parentCategoryId || categoryId;
if (checkCategoryId) {
selectedCategories.add(parseInt(checkCategoryId));
}
total += price;
}
});
selectedParts = items;
// 必須カテゴリがすべて選択されているかチェック
const allRequiredSelected = requiredCategories.every(catId => selectedCategories.has(catId));
if (items.length > 0) {
// 合計金額の表示
if (allRequiredSelected) {
// 内部料金計算: 合計 = パーツ合計 × (1 - 割引率/100) + 追加料金
let finalTotal = total * (1 - discountRate / 100) + additionalCharge;
finalTotal = Math.round(finalTotal); // 四捨五入
$('#totalPrice').text(finalTotal.toLocaleString());
$('#stickyTotalPrice').text(finalTotal.toLocaleString()); // 固定サマリーも更新
$('#totalPriceArea').removeClass('hidden');
$('#requiredWarning').removeClass('active');
$('#customerForm').addClass('active');
$('#submitSection').addClass('active');
$('#purchaseSection').show();
} else {
$('#totalPrice').text('---');
$('#stickyTotalPrice').text('---'); // 固定サマリーもクリア
$('#totalPriceArea').addClass('hidden');
$('#requiredWarning').addClass('active');
$('#customerForm').removeClass('active');
$('#submitSection').removeClass('active');
$('#purchaseSection').hide();
}
} else {
$('#totalPrice').text('---');
$('#stickyTotalPrice').text('---'); // 固定サマリーもクリア
$('#totalPriceArea').addClass('hidden');
$('#requiredWarning').removeClass('active');
$('#customerForm').removeClass('active');
$('#submitSection').removeClass('active');
$('#purchaseSection').hide();
}
}
// 送信
$('#submitBtn').on('click', function() {
if (selectedParts.length === 0) {
alert('パーツを選択してください。');
return;
}
const form = $('#estimateForm')[0];
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const formData = new FormData(form);
formData.append('items', JSON.stringify(selectedParts));
$('#submitBtn').prop('disabled', true);
$('#loading').addClass('active');
$.ajax({
url: '{{ url('nz_estimate_simulator_send') }}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
headers: {
'x-csrf-token': $('meta[name="x-csrf-token"]').attr('content')
},
success: function(response) {
$('#loading').removeClass('active');
if (response.success) {
$('.category-section, .estimate-summary, .customer-form, .submit-section').hide();
$('#completeMessage').addClass('active');
} else {
alert(response.message || '送信に失敗しました。');
$('#submitBtn').prop('disabled', false);
}
},
error: function(xhr, status, error) {
$('#loading').removeClass('active');
let errorMessage = '送信中にエラーが発生しました。';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
} else if (xhr.status === 0) {
errorMessage = 'ネットワークエラーが発生しました。インターネット接続を確認してください。';
} else if (xhr.status === 500) {
errorMessage = 'サーバーエラーが発生しました。しばらく時間をおいて再度お試しください。';
} else if (xhr.status === 403) {
errorMessage = 'セキュリティエラーが発生しました。ページを再読み込みして再度お試しください。';
}
alert(errorMessage);
console.error('Error details:', {
status: xhr.status,
statusText: xhr.statusText,
responseText: xhr.responseText,
error: error
});
$('#submitBtn').prop('disabled', false);
}
});
});
// 今すぐ購入ボタン
$('#purchaseBtn').on('click', function(e) {
e.preventDefault();
console.log('購入ボタンがクリックされました');
if (selectedParts.length === 0) {
alert('パーツが選択されていません');
return;
}
// パーツ合計を計算
const partsTotal = Math.floor(selectedParts.reduce((sum, item) => sum + item.price, 0));
// 内部料金調整を適用: 合計 = パーツ合計 × (1 - 割引率/100) + 追加料金
let finalTotal = partsTotal * (1 - discountRate / 100) + additionalCharge;
finalTotal = Math.floor(finalTotal); // 小数点以下切り捨て
console.log('カート追加:', {
partsTotal: partsTotal,
discountRate: discountRate,
additionalCharge: additionalCharge,
finalTotal: finalTotal,
selectedParts: selectedParts
});
// ボタンを無効化
$(this).prop('disabled', true).text('商品作成中...');
// カートに追加リクエスト
const ajaxUrl = '/estimate_simulator/add_to_cart';
console.log('AJAX URL:', ajaxUrl);
$.ajax({
url: ajaxUrl,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify({
selected_parts: selectedParts,
total_price: finalTotal
}),
beforeSend: function() {
console.log('AJAXリクエスト送信開始');
},
success: function(response) {
console.log('AJAX成功:', response);
if (response.success) {
console.log('リダイレクト開始:', response.product_url);
// 複数の方法でリダイレクトを試行
try {
window.location.href = response.product_url;
} catch(e) {
console.error('location.href失敗:', e);
try {
window.location.assign(response.product_url);
} catch(e2) {
console.error('location.assign失敗:', e2);
window.location.replace(response.product_url);
}
}
} else {
alert(response.message || '商品作成に失敗しました');
$('#purchaseBtn').prop('disabled', false).text('🛒 すぐに購入');
}
},
error: function(xhr, status, error) {
console.log('AJAXエラー:', {status: xhr.status, error: error, responseText: xhr.responseText});
let errorMessage = '商品作成中にエラーが発生しました';
if (xhr.responseJSON && xhr.responseJSON.message) {
errorMessage = xhr.responseJSON.message;
}
alert(errorMessage);
$('#purchaseBtn').prop('disabled', false).text('🛒 すぐに購入');
}
});
});
// スマホ用:スクロール検知で固定サマリーを表示/非表示
function initStickySummary() {
if (window.innerWidth > 768) {
return; // タブレット以上は無効
}
const $originalSummary = $('.estimate-summary').first();
const $stickySummary = $('#stickySummary');
if ($originalSummary.length === 0 || $stickySummary.length === 0) {
return;
}
function checkSummaryPosition() {
const summaryTop = $originalSummary.offset().top;
const summaryBottom = summaryTop + $originalSummary.outerHeight();
const scrollTop = $(window).scrollTop();
const windowBottom = scrollTop + $(window).height();
// 元のサマリーが画面外に出たら固定サマリーを表示
if (summaryBottom < scrollTop || summaryTop > windowBottom) {
$stickySummary.addClass('visible');
$('body').addClass('sticky-summary-active');
} else {
$stickySummary.removeClass('visible');
$('body').removeClass('sticky-summary-active');
}
}
$(window).on('scroll', checkSummaryPosition);
$(window).on('resize', checkSummaryPosition);
// 初回チェック(少し遅延させる)
setTimeout(checkSummaryPosition, 500);
}
// DOMが完全に読み込まれた後に初期化
initStickySummary();
});
</script>
{% endblock %}