<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
/* @NZEstimateSystem42/default/simulator.twig */
class __TwigTemplate_ffdd72869d9b4d52aecce8a5eca7d16f67393c231120a1c96baa867d6de84b47 extends \Eccube\Twig\Template
{
private $source;
private $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->blocks = [
'main' => [$this, 'block_main'],
];
}
protected function doGetParent(array $context)
{
// line 1
return "default_frame.twig";
}
protected function doDisplay(array $context, array $blocks = [])
{
$macros = $this->macros;
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e->enter($__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@NZEstimateSystem42/default/simulator.twig"));
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02 = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02->enter($__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "@NZEstimateSystem42/default/simulator.twig"));
$this->parent = $this->loadTemplate("default_frame.twig", "@NZEstimateSystem42/default/simulator.twig", 1);
$this->parent->display($context, array_merge($this->blocks, $blocks));
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e->leave($__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e_prof);
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02->leave($__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02_prof);
}
// line 3
public function block_main($context, array $blocks = [])
{
$macros = $this->macros;
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e->enter($__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "main"));
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02 = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02->enter($__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "main"));
// line 4
echo "<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\">
";
// line 895
if (twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 895, $this->source); })()), "pageTitle", [], "any", false, false, false, 895)) {
// line 896
echo " ";
echo twig_replace_filter(twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 896, $this->source); })()), "pageTitle", [], "any", false, false, false, 896), ["<" => "<", ">" => ">", """ => "\"", "'" => "'", "&" => "&"]);
echo "
";
} else {
// line 898
echo " 見積もりシミュレーター
";
}
// line 900
echo " </h1>
<div class=\"nz-estimate-description\">
";
// line 902
if (twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 902, $this->source); })()), "pageDescription", [], "any", false, false, false, 902)) {
// line 903
echo " ";
echo twig_replace_filter(twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 903, $this->source); })()), "pageDescription", [], "any", false, false, false, 903), ["<" => "<", ">" => ">", """ => "\"", "'" => "'", "&" => "&"]);
echo "
";
} else {
// line 905
echo " パーツを選択して、お見積もりを作成いただけます。
";
}
// line 907
echo " </div>
</div>
<div class=\"nz-estimate-body\">
";
// line 911
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable((isset($context["categoriesWithParts"]) || array_key_exists("categoriesWithParts", $context) ? $context["categoriesWithParts"] : (function () { throw new RuntimeError('Variable "categoriesWithParts" does not exist.', 911, $this->source); })()));
foreach ($context['_seq'] as $context["_key"] => $context["item"]) {
// line 912
echo " <div class=\"category-section\" data-category-id=\"";
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 912), "id", [], "any", false, false, false, 912), "html", null, true);
echo "\">
<div class=\"category-row\">
<div class=\"category-header\">
<h2 class=\"category-title\">";
// line 915
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 915), "name", [], "any", false, false, false, 915), "html", null, true);
echo "</h2>
";
// line 916
if (twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 916), "isRequired", [], "any", false, false, false, 916)) {
// line 917
echo " <span class=\"category-required-badge\">必須</span>
";
} else {
// line 919
echo " <span class=\"category-optional-badge\">任意</span>
";
}
// line 921
echo " </div>
";
// line 924
echo " <select class=\"parts-select\"
data-category-id=\"";
// line 925
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 925), "id", [], "any", false, false, false, 925), "html", null, true);
echo "\"
data-required=\"";
// line 926
echo ((twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 926), "isRequired", [], "any", false, false, false, 926)) ? ("1") : ("0"));
echo "\"
data-category-name=\"";
// line 927
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 927), "name", [], "any", false, false, false, 927), "html", null, true);
echo "\">
<option value=\"\">選択してください</option>
";
// line 929
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable(twig_get_attribute($this->env, $this->source, $context["item"], "parts", [], "any", false, false, false, 929));
foreach ($context['_seq'] as $context["_key"] => $context["parts"]) {
// line 930
echo " <option value=\"";
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "id", [], "any", false, false, false, 930), "html", null, true);
echo "\"
data-price=\"";
// line 931
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "price", [], "any", false, false, false, 931), "html", null, true);
echo "\"
data-name=\"";
// line 932
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "productName", [], "any", false, false, false, 932), "html", null, true);
echo "\"
data-child-category-id=\"";
// line 933
((twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 933)) ? (print (twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 933), "html", null, true))) : (print ("")));
echo "\"
data-child-filter-keywords=\"";
// line 934
((twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 934)) ? (print (twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 934), "html", null, true))) : (print ("")));
echo "\">
";
// line 935
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "productName", [], "any", false, false, false, 935), "html", null, true);
echo "
</option>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['parts'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 938
echo " </select>
</div>
</div>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['item'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 942
echo "
";
// line 944
echo " <script type=\"application/json\" id=\"allPartsData\">
[
";
// line 946
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable((isset($context["categoriesWithParts"]) || array_key_exists("categoriesWithParts", $context) ? $context["categoriesWithParts"] : (function () { throw new RuntimeError('Variable "categoriesWithParts" does not exist.', 946, $this->source); })()));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["_key"] => $context["item"]) {
// line 947
echo " ";
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable(twig_get_attribute($this->env, $this->source, $context["item"], "parts", [], "any", false, false, false, 947));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["_key"] => $context["parts"]) {
// line 948
echo " {
\"id\": ";
// line 949
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "id", [], "any", false, false, false, 949), "html", null, true);
echo ",
\"category_id\": ";
// line 950
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 950), "id", [], "any", false, false, false, 950), "html", null, true);
echo ",
\"category_name\": ";
// line 951
echo json_encode(twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 951), "name", [], "any", false, false, false, 951));
echo ",
\"name\": ";
// line 952
echo json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "productName", [], "any", false, false, false, 952));
echo ",
\"model_number\": ";
// line 953
echo json_encode(((twig_get_attribute($this->env, $this->source, $context["parts"], "modelNumber", [], "any", true, true, false, 953)) ? (_twig_default_filter(twig_get_attribute($this->env, $this->source, $context["parts"], "modelNumber", [], "any", false, false, false, 953), "")) : ("")));
echo ",
\"price\": ";
// line 954
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "price", [], "any", false, false, false, 954), "html", null, true);
echo ",
\"child_category_id\": ";
// line 955
echo ((twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 955)) ? (json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 955))) : ("null"));
echo ",
\"child_filter_keywords\": ";
// line 956
echo ((twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 956)) ? (json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 956))) : ("null"));
echo "
}";
// line 957
if (( !twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["loop"], "parent", [], "any", false, false, false, 957), "loop", [], "any", false, false, false, 957), "last", [], "any", false, false, false, 957) || !twig_get_attribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 957))) {
echo ",";
}
// line 958
echo " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['length'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['parts'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 959
echo " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['length'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['item'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 960
echo " ]
</script>
";
// line 964
echo " <script type=\"application/json\" id=\"allCategoriesData\">
[
";
// line 966
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable((isset($context["allCategoriesWithParts"]) || array_key_exists("allCategoriesWithParts", $context) ? $context["allCategoriesWithParts"] : (function () { throw new RuntimeError('Variable "allCategoriesWithParts" does not exist.', 966, $this->source); })()));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["_key"] => $context["item"]) {
// line 967
echo " {
\"id\": ";
// line 968
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 968), "id", [], "any", false, false, false, 968), "html", null, true);
echo ",
\"name\": ";
// line 969
echo json_encode(twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 969), "name", [], "any", false, false, false, 969));
echo ",
\"is_required\": ";
// line 970
echo ((twig_get_attribute($this->env, $this->source, twig_get_attribute($this->env, $this->source, $context["item"], "category", [], "any", false, false, false, 970), "isRequired", [], "any", false, false, false, 970)) ? ("true") : ("false"));
echo ",
\"parts\": [
";
// line 972
$context['_parent'] = $context;
$context['_seq'] = twig_ensure_traversable(twig_get_attribute($this->env, $this->source, $context["item"], "parts", [], "any", false, false, false, 972));
$context['loop'] = [
'parent' => $context['_parent'],
'index0' => 0,
'index' => 1,
'first' => true,
];
if (is_array($context['_seq']) || (is_object($context['_seq']) && $context['_seq'] instanceof \Countable)) {
$length = count($context['_seq']);
$context['loop']['revindex0'] = $length - 1;
$context['loop']['revindex'] = $length;
$context['loop']['length'] = $length;
$context['loop']['last'] = 1 === $length;
}
foreach ($context['_seq'] as $context["_key"] => $context["parts"]) {
// line 973
echo " {
\"id\": ";
// line 974
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "id", [], "any", false, false, false, 974), "html", null, true);
echo ",
\"name\": ";
// line 975
echo json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "productName", [], "any", false, false, false, 975));
echo ",
\"model_number\": ";
// line 976
echo json_encode(((twig_get_attribute($this->env, $this->source, $context["parts"], "modelNumber", [], "any", true, true, false, 976)) ? (_twig_default_filter(twig_get_attribute($this->env, $this->source, $context["parts"], "modelNumber", [], "any", false, false, false, 976), "")) : ("")));
echo ",
\"price\": ";
// line 977
echo twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, $context["parts"], "price", [], "any", false, false, false, 977), "html", null, true);
echo ",
\"child_category_id\": ";
// line 978
echo ((twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 978)) ? (json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "childCategoryId", [], "any", false, false, false, 978))) : ("null"));
echo ",
\"child_filter_keywords\": ";
// line 979
echo ((twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 979)) ? (json_encode(twig_get_attribute($this->env, $this->source, $context["parts"], "childFilterKeywords", [], "any", false, false, false, 979))) : ("null"));
echo "
}";
// line 980
if ( !twig_get_attribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 980)) {
echo ",";
}
// line 981
echo " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['length'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['parts'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 982
echo " ]
}";
// line 983
if ( !twig_get_attribute($this->env, $this->source, $context["loop"], "last", [], "any", false, false, false, 983)) {
echo ",";
}
// line 984
echo " ";
++$context['loop']['index0'];
++$context['loop']['index'];
$context['loop']['first'] = false;
if (isset($context['loop']['length'])) {
--$context['loop']['revindex0'];
--$context['loop']['revindex'];
$context['loop']['last'] = 0 === $context['loop']['revindex0'];
}
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_iterated'], $context['_key'], $context['item'], $context['_parent'], $context['loop']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 985
echo " ]
</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>
";
// line 1026
if (twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1026, $this->source); })()), "purchaseNoticeText", [], "any", false, false, false, 1026)) {
// line 1027
echo " <div class=\"purchase-notice\">
";
// line 1028
echo twig_nl2br(twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1028, $this->source); })()), "purchaseNoticeText", [], "any", false, false, false, 1028), "html", null, true));
echo "
</div>
";
}
// line 1031
echo " </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>
";
// line 1074
if (twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1074, $this->source); })()), "noticeText", [], "any", false, false, false, 1074)) {
// line 1075
echo " <div class=\"notice-text\">
";
// line 1076
echo twig_nl2br(twig_escape_filter($this->env, twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1076, $this->source); })()), "noticeText", [], "any", false, false, false, 1076), "html", null, true));
echo "
</div>
";
}
// line 1079
echo " </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>
<!-- フリーエリア -->
";
// line 1101
if (twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1101, $this->source); })()), "freeAreaHtml", [], "any", false, false, false, 1101)) {
// line 1102
echo "<div class=\"nz-free-area\">
";
// line 1103
echo twig_get_attribute($this->env, $this->source, (isset($context["config"]) || array_key_exists("config", $context) ? $context["config"] : (function () { throw new RuntimeError('Variable "config" does not exist.', 1103, $this->source); })()), "freeAreaHtml", [], "any", false, false, false, 1103);
echo "
</div>
";
}
// line 1106
echo "
<script>
\$(function() {
let selectedParts = [];
// 内部料金設定(顧客には非表示)
const additionalCharge = ";
// line 1112
echo twig_escape_filter($this->env, ((twig_get_attribute($this->env, $this->source, ($context["config"] ?? null), "additionalCharge", [], "any", true, true, false, 1112)) ? (_twig_default_filter(twig_get_attribute($this->env, $this->source, ($context["config"] ?? null), "additionalCharge", [], "any", false, false, false, 1112), 0)) : (0)), "html", null, true);
echo ";
const discountRate = ";
// line 1113
echo twig_escape_filter($this->env, ((twig_get_attribute($this->env, $this->source, ($context["config"] ?? null), "discountRate", [], "any", true, true, false, 1113)) ? (_twig_default_filter(twig_get_attribute($this->env, $this->source, ($context["config"] ?? null), "discountRate", [], "any", false, false, false, 1113), 0)) : (0)), "html", null, true);
echo ";
// データ読み込み
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: '";
// line 1346
echo $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getUrl("nz_estimate_simulator_send");
echo "',
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>
";
$__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02->leave($__internal_319393461309892924ff6e74d6d6e64287df64b63545b994e100d4ab223aed02_prof);
$__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e->leave($__internal_085b0142806202599c7fe3b329164a92397d8978207a37e79d70b8c52599e33e_prof);
}
public function getTemplateName()
{
return "@NZEstimateSystem42/default/simulator.twig";
}
public function isTraitable()
{
return false;
}
public function getDebugInfo()
{
return array ( 1710 => 1346, 1474 => 1113, 1470 => 1112, 1462 => 1106, 1456 => 1103, 1453 => 1102, 1451 => 1101, 1427 => 1079, 1421 => 1076, 1418 => 1075, 1416 => 1074, 1371 => 1031, 1365 => 1028, 1362 => 1027, 1360 => 1026, 1317 => 985, 1303 => 984, 1299 => 983, 1296 => 982, 1282 => 981, 1278 => 980, 1274 => 979, 1270 => 978, 1266 => 977, 1262 => 976, 1258 => 975, 1254 => 974, 1251 => 973, 1234 => 972, 1229 => 970, 1225 => 969, 1221 => 968, 1218 => 967, 1201 => 966, 1197 => 964, 1192 => 960, 1178 => 959, 1164 => 958, 1160 => 957, 1156 => 956, 1152 => 955, 1148 => 954, 1144 => 953, 1140 => 952, 1136 => 951, 1132 => 950, 1128 => 949, 1125 => 948, 1107 => 947, 1090 => 946, 1086 => 944, 1083 => 942, 1074 => 938, 1065 => 935, 1061 => 934, 1057 => 933, 1053 => 932, 1049 => 931, 1044 => 930, 1040 => 929, 1035 => 927, 1031 => 926, 1027 => 925, 1024 => 924, 1020 => 921, 1016 => 919, 1012 => 917, 1010 => 916, 1006 => 915, 999 => 912, 995 => 911, 989 => 907, 985 => 905, 979 => 903, 977 => 902, 973 => 900, 969 => 898, 963 => 896, 961 => 895, 68 => 4, 58 => 3, 35 => 1,);
}
public function getSourceContext()
{
return new Source("{% 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 %}
", "@NZEstimateSystem42/default/simulator.twig", "/home/noie373zam/public_html/noiezam-ec/app/Plugin/NZEstimateSystem42/Resource/template/default/simulator.twig");
}
}