Files
ewoooc/templates/sales_analysis.html
OoO 72daa53040
Some checks failed
CD Pipeline / deploy (push) Has been cancelled
快取業績分析頁面資料
2026-05-13 11:56:08 +08:00

887 lines
47 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends 'ewoooc_base.html' %}
{% block title %}業績分析 - EwoooC{% endblock %}
{#
v3 改寫重點:
- 從 inline <style> 519 page-sales-analysis.css
- 移除控制面板 header 的紫色漸層背景
- 移除引導訊息區的 #0d6efd 藍色佔比硬編碼
- 6 個 KPI 卡bg-primary / bg-danger / bg-success / bg-info / bg-warning / #20c997
→ 5 個語意 KPI 變體(.kpi--revenue / .kpi--cost / .kpi--profit / .kpi--qty / .kpi--sku
- ABC 三類卡text-danger / text-warning / text-success → P0/P1/P2 token
- YoY 成長率:#e8f5e9 綠底硬編 → 用 olive accent token
- Marketing chart titlestext-primary 藍 / text-success 綠 → page-accent / olive
#}
{% block extra_css %}
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-sales-analysis.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-sales-analysis-bem.css') }}">
{% endblock %}
{% block ewooo_content %}
<!-- ============ Loading Overlay (warm reskin) ============ -->
<div id="loadingOverlay" data-screen-label="loading">
<div class="loading-logo-container">
<div class="logo-pulse"></div>
<div class="logo-ring"></div>
<div class="logo-ring-inner"></div>
<div class="orbit-particles">
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
<div class="orbit-particle"></div>
</div>
<div class="sparkles">
{% for _ in range(6) %}<div class="sparkle"></div>{% endfor %}
</div>
<div class="loading-logo">EwoooC</div>
</div>
<div class="loading-text" id="loadingText">
<i class="fas fa-chart-bar me-2" aria-hidden="true"></i>正在載入數據...
</div>
<div class="loading-progress"><div class="loading-progress-bar"></div></div>
<div class="loading-hint" id="loadingHint">大量資料可能需要較長時間,請稍候</div>
</div>
<div class="sales-analysis-page" data-page-group="analytics" data-screen-label="sales-analysis">
{% include 'components/_analysis_report_tabs.html' %}
<!-- ============ Page header ============ -->
<header class="sa-page-head">
<div class="sa-page-head__lead">
<h4 class="sa-page-head__title">
<i class="fas fa-chart-pie" aria-hidden="true"></i>
業績分析儀表板
</h4>
{% if db_data_range %}
<span class="sa-tag sa-tag--neutral">
<i class="fas fa-calendar-alt" aria-hidden="true"></i>
資料期間:{{ db_data_range }}
</span>
{% endif %}
</div>
{% if not no_filter %}
<div class="sa-page-head__meta">
<span class="sa-tag sa-tag--accent">
<i class="fas fa-database" aria-hidden="true"></i>
{% if start_date or end_date %}
{% if start_date and end_date %}{{ start_date }} ~ {{ end_date }}
{% elif start_date %}{{ start_date }} 起
{% else %}{{ end_date }} 止{% endif %}
{% elif data_range_months == 0 %}全部資料
{% else %}最近 {{ data_range_months }} 個月{% endif %}
<span class="sa-tag__sep">·</span>
{{ "{:,}".format(total_records) }} 筆
</span>
</div>
{% endif %}
</header>
{% if error %}
<div class="alert alert-warning sa-alert">
<i class="fas fa-exclamation-triangle me-2" aria-hidden="true"></i>{{ error }}
<div class="mt-2">
<a href="/settings" class="btn btn-sm btn-outline-dark">前往匯入資料</a>
</div>
</div>
{% else %}
<!-- ============ 控制面板 (篩選器) ============ -->
<section class="card sa-filter-card sa-filter-card--sticky">
<div class="card-header sa-filter-head">
<h5 class="sa-filter-head__title">
<i class="fas fa-sliders-h" aria-hidden="true"></i>進階篩選與分析
</h5>
<div class="btn-group sa-metric-switch" role="group" aria-label="分析維度切換">
{% set metric_opts = [
{'v':'amount', 'label':'依金額分析', 'icon':'dollar-sign'},
{'v':'qty', 'label':'依銷售量分析', 'icon':'box'}
] %}
{% if cols.cost or cols.profit %}
{% set _ = metric_opts.append({'v':'profit', 'label':'依毛利分析', 'icon':'chart-line'}) %}
{% endif %}
{% for m in metric_opts %}
<input type="radio" class="btn-check" name="metric" id="metric{{ m.v|capitalize }}"
value="{{ m.v }}" autocomplete="off"
onchange="setFilter('metric', '{{ m.v }}')"
{% if selected_metric == m.v %}checked{% endif %}>
<label class="btn btn-sm sa-metric-switch__btn" for="metric{{ m.v|capitalize }}">
<i class="fas fa-{{ m.icon }} me-1" aria-hidden="true"></i>{{ m.label }}
</label>
{% endfor %}
</div>
</div>
<div class="card-body sa-filter-body">
<form method="GET" action="/sales_analysis" class="row g-3">
<input type="hidden" name="metric" value="{{ selected_metric }}">
<!-- ─── 第一區:資料範圍 ─── -->
<div class="col-12">
<div class="sa-filter-group">
<h6 class="sa-filter-group__title">
<i class="fas fa-calendar-check sa-filter-group__icon sa-filter-group__icon--accent" aria-hidden="true"></i>資料範圍設定
</h6>
<div class="row g-2">
<div class="col-md-3">
<label class="form-label">
<i class="fas fa-database me-1" aria-hidden="true"></i>資料載入範圍
</label>
<select name="data_range" class="form-select" onchange="handleDataRangeChange(this)">
<option value="" {% if not request.args.get('data_range') %}selected{% endif %}>-- 請選擇 --</option>
{% for opt in [(1,'最近 1 個月 (推薦)'), (3,'最近 3 個月'), (6,'最近 6 個月'), (12,'最近 12 個月'), (0,'全部資料')] %}
<option value="{{ opt.0 }}" {% if request.args.get('data_range') == opt.0|string %}selected{% endif %}>{{ opt.1 }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">
<i class="fas fa-calendar-alt me-1" aria-hidden="true"></i>開始日期
<small class="text-muted">(選填)</small>
</label>
<input type="text" id="start_date" name="start_date"
class="form-control flatpickr-input"
placeholder="選擇開始日期"
value="{{ request.args.get('start_date', '') }}"
readonly="readonly">
</div>
<div class="col-md-3">
<label class="form-label">
<i class="fas fa-calendar-check me-1" aria-hidden="true"></i>結束日期
<small class="text-muted">(選填)</small>
</label>
<div class="input-group">
<input type="text" id="end_date" name="end_date"
class="form-control flatpickr-input"
placeholder="選擇結束日期"
value="{{ request.args.get('end_date', '') }}"
readonly="readonly">
{% if request.args.get('start_date') or request.args.get('end_date') %}
<button type="button" class="btn btn-outline-danger" onclick="clearDateRange()" title="清除日期範圍">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
{% endif %}
</div>
</div>
<div class="col-md-3 d-flex align-items-end gap-2">
<a href="/sales_analysis" class="btn btn-outline-secondary flex-fill">
<i class="fas fa-redo me-1" aria-hidden="true"></i>重置
</a>
<button type="submit" class="btn btn-primary flex-fill">
<i class="fas fa-search me-1" aria-hidden="true"></i>查詢
</button>
</div>
</div>
</div>
</div>
<!-- ─── 第二區:商品屬性 ─── -->
<div class="col-12">
<div class="sa-filter-group">
<h6 class="sa-filter-group__title">
<i class="fas fa-tags sa-filter-group__icon sa-filter-group__icon--info" aria-hidden="true"></i>商品屬性篩選
</h6>
<div class="row g-2">
{% set attr_filters = [
{'key':'category', 'label':'商品分類', 'icon':'layer-group', 'all_label':'全部分類', 'options': all_categories, 'selected': selected_category, 'show': true},
{'key':'brand', 'label':'品牌', 'icon':'tag', 'all_label':'全部品牌', 'options': all_brands, 'selected': selected_brand, 'show': all_brands},
{'key':'vendor', 'label':'供應商', 'icon':'truck', 'all_label':'全部廠商', 'options': all_vendors, 'selected': selected_vendor, 'show': all_vendors}
] %}
{% for f in attr_filters %}
{% if f.show %}
<div class="col-md-4">
<label class="form-label"><i class="fas fa-{{ f.icon }} me-1" aria-hidden="true"></i>{{ f.label }}</label>
<div class="btn-group w-100 sa-dropdown-filter">
<button type="button" class="btn btn-outline-secondary dropdown-toggle w-100 text-start text-truncate" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-{{ f.icon }} me-2 text-muted" aria-hidden="true"></i>
{{ f.selected if f.selected != 'all' else f.all_label }}
</button>
<ul class="dropdown-menu w-100 sa-dropdown-menu">
<li class="px-2 py-1">
<input type="text" class="form-control form-control-sm" placeholder="搜尋..." onkeyup="filterDropdown(this)">
</li>
<li><a class="dropdown-item" href="javascript:setFilter('{{ f.key }}', 'all')">{{ f.all_label }}</a></li>
<li><hr class="dropdown-divider"></li>
{% for opt in f.options %}
<li><a class="dropdown-item" href="javascript:setFilter('{{ f.key }}', '{{ opt }}')">{{ opt }}</a></li>
{% endfor %}
</ul>
<input type="hidden" name="{{ f.key }}" value="{{ f.selected }}">
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<!-- ─── 第三區:行銷與付款 ─── -->
{% if all_activities or all_payments %}
<div class="col-12">
<div class="sa-filter-group">
<h6 class="sa-filter-group__title">
<i class="fas fa-bullhorn sa-filter-group__icon sa-filter-group__icon--warning" aria-hidden="true"></i>行銷與付款
</h6>
<div class="row g-2">
{% set mkt_filters = [
{'key':'activity', 'label':'行銷活動', 'icon':'bullhorn', 'all_label':'全部活動', 'options': all_activities, 'selected': selected_activity, 'show': all_activities, 'col':'col-md-6'},
{'key':'payment', 'label':'付款方式', 'icon':'credit-card', 'all_label':'全部付款方式', 'options': all_payments, 'selected': selected_payment, 'show': all_payments, 'col':'col-md-6'}
] %}
{% for f in mkt_filters %}
{% if f.show %}
<div class="{{ f.col }}">
<label class="form-label"><i class="fas fa-{{ f.icon }} me-1" aria-hidden="true"></i>{{ f.label }}</label>
<div class="btn-group w-100 sa-dropdown-filter">
<button type="button" class="btn btn-outline-secondary dropdown-toggle w-100 text-start text-truncate" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-{{ f.icon }} me-2 text-muted" aria-hidden="true"></i>
{{ f.selected if f.selected != 'all' else f.all_label }}
</button>
<ul class="dropdown-menu w-100 sa-dropdown-menu">
<li class="px-2 py-1">
<input type="text" class="form-control form-control-sm" placeholder="搜尋..." onkeyup="filterDropdown(this)">
</li>
<li><a class="dropdown-item" href="javascript:setFilter('{{ f.key }}', 'all')">{{ f.all_label }}</a></li>
<li><hr class="dropdown-divider"></li>
{% for opt in f.options %}
<li><a class="dropdown-item" href="javascript:setFilter('{{ f.key }}', '{{ opt }}')">{{ opt }}</a></li>
{% endfor %}
</ul>
<input type="hidden" name="{{ f.key }}" value="{{ f.selected }}">
</div>
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
{% endif %}
<!-- ─── 第四區:時間維度 ─── -->
{% if cols.date %}
<div class="col-12">
<div class="sa-filter-group">
<h6 class="sa-filter-group__title">
<i class="fas fa-clock sa-filter-group__icon sa-filter-group__icon--danger" aria-hidden="true"></i>時間維度篩選
</h6>
<div class="row g-2">
<!-- 月份 -->
<div class="col-md-4">
<label class="form-label"><i class="fas fa-calendar-alt me-1" aria-hidden="true"></i>月份</label>
<div class="btn-group w-100 sa-dropdown-filter">
<button type="button" class="btn btn-outline-secondary dropdown-toggle w-100 text-start text-truncate" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-calendar-alt me-2 text-muted" aria-hidden="true"></i>
{{ selected_month if selected_month != 'all' else '全部月份' }}
</button>
<ul class="dropdown-menu w-100 sa-dropdown-menu">
<li><a class="dropdown-item" href="javascript:setFilter('month', 'all')">全部月份</a></li>
<li><hr class="dropdown-divider"></li>
{% for m in all_months %}
<li><a class="dropdown-item" href="javascript:setFilter('month', '{{ m }}')">{{ m }}</a></li>
{% endfor %}
</ul>
<input type="hidden" name="month" value="{{ selected_month }}">
</div>
</div>
<!-- 星期 -->
{% set dow_labels = {'all':'全部星期','0':'週一','1':'週二','2':'週三','3':'週四','4':'週五','5':'週六','6':'週日'} %}
<div class="col-md-4">
<label class="form-label"><i class="fas fa-calendar-day me-1" aria-hidden="true"></i>星期</label>
<div class="btn-group w-100 sa-dropdown-filter">
<button type="button" class="btn btn-outline-secondary dropdown-toggle w-100 text-start text-truncate" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-calendar-day me-2 text-muted" aria-hidden="true"></i>
{{ dow_labels[selected_dow|string] }}
</button>
<ul class="dropdown-menu w-100 sa-dropdown-menu">
{% for k, label in [('all','全部星期'),('0','週一'),('1','週二'),('2','週三'),('3','週四'),('4','週五'),('5','週六'),('6','週日')] %}
<li><a class="dropdown-item" href="javascript:setFilter('dow', '{{ k }}')">{{ label }}</a></li>
{% if loop.first %}<li><hr class="dropdown-divider"></li>{% endif %}
{% endfor %}
</ul>
<input type="hidden" name="dow" value="{{ selected_dow }}">
</div>
</div>
<!-- 時段 -->
<div class="col-md-4">
<label class="form-label"><i class="fas fa-clock me-1" aria-hidden="true"></i>時段</label>
<div class="btn-group w-100 sa-dropdown-filter">
<button type="button" class="btn btn-outline-secondary dropdown-toggle w-100 text-start text-truncate" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fas fa-clock me-2 text-muted" aria-hidden="true"></i>
{{ selected_hour + ':00' if selected_hour != 'all' else '全部時段' }}
</button>
<ul class="dropdown-menu w-100 sa-dropdown-menu">
<li><a class="dropdown-item" href="javascript:setFilter('hour', 'all')">全部時段</a></li>
<li><hr class="dropdown-divider"></li>
{% for h in range(24) %}
<li><a class="dropdown-item" href="javascript:setFilter('hour', '{{ h }}')">{{ "%02d"|format(h) }}:00</a></li>
{% endfor %}
</ul>
<input type="hidden" name="hour" value="{{ selected_hour }}">
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- ─── 第五區:數值與關鍵字 ─── -->
<div class="col-12">
<div class="sa-filter-group">
<h6 class="sa-filter-group__title">
<i class="fas fa-search sa-filter-group__icon sa-filter-group__icon--success" aria-hidden="true"></i>數值與關鍵字篩選
</h6>
<div class="row g-2">
<div class="col-md-4">
<label class="form-label"><i class="fas fa-dollar-sign me-1" aria-hidden="true"></i>單價區間 ($)</label>
<div class="input-group">
<input type="number" name="min_price" class="form-control" placeholder="Min" value="{{ min_price }}">
<span class="input-group-text">~</span>
<input type="number" name="max_price" class="form-control" placeholder="Max" value="{{ max_price }}">
</div>
</div>
{% if cols.cost or cols.profit %}
<div class="col-md-4">
<label class="form-label"><i class="fas fa-percentage me-1" aria-hidden="true"></i>毛利率區間 (%)</label>
<div class="input-group">
<input type="number" name="min_margin" class="form-control" placeholder="Min" value="{{ min_margin }}">
<span class="input-group-text">~</span>
<input type="number" name="max_margin" class="form-control" placeholder="Max" value="{{ max_margin }}">
</div>
</div>
{% endif %}
<div class="col-md-4">
<label class="form-label"><i class="fas fa-search me-1" aria-hidden="true"></i>關鍵字搜尋</label>
<div class="input-group">
<span class="input-group-text"><i class="fas fa-search" aria-hidden="true"></i></span>
<input type="text" name="keyword" class="form-control" placeholder="輸入商品名稱..." value="{{ keyword }}">
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</section>
<!-- ============ 引導訊息(首次進入) ============ -->
{% if no_filter %}
<section class="sa-empty">
<div class="sa-empty__inner">
<div class="sa-empty__icon">
<i class="fas fa-chart-line" aria-hidden="true"></i>
</div>
<h3 class="sa-empty__title">
<i class="fas fa-hand-point-up me-2" aria-hidden="true"></i>開始分析您的業績數據
</h3>
<p class="sa-empty__lead">
請在上方<strong>「進階篩選」</strong>區域選擇以下任一條件開始分析:
</p>
<div class="row g-3 sa-empty__hints">
<div class="col-md-6">
<div class="sa-empty__hint sa-empty__hint--accent">
<h5><i class="fas fa-database me-2" aria-hidden="true"></i>資料載入範圍</h5>
<p>快速選擇最近 1/3/6/12 個月或全部資料</p>
<p class="sa-empty__hint-foot"><i class="fas fa-star me-1" aria-hidden="true"></i>推薦新手使用</p>
</div>
</div>
<div class="col-md-6">
<div class="sa-empty__hint sa-empty__hint--olive">
<h5><i class="fas fa-calendar-alt me-2" aria-hidden="true"></i>自訂日期區間</h5>
<p>精確指定開始/結束日期進行分析</p>
<p class="sa-empty__hint-foot"><i class="fas fa-clock me-1" aria-hidden="true"></i>適合特定區間分析</p>
</div>
</div>
</div>
<div class="alert alert-info sa-empty__tip">
<i class="fas fa-info-circle me-2" aria-hidden="true"></i>
<strong>提示:</strong>選擇任一條件後,系統將自動載入並顯示詳細的圖表與數據分析
</div>
</div>
</section>
{% else %}
<!-- ============ KPI 區塊v3語意 variant 取代 Bootstrap bg-* ============ -->
{% set kpi_cards = [
{'variant':'revenue','label':'總業績 (Revenue)', 'value': '${:,.0f}'.format(kpi.revenue), 'icon':'coins', 'show': true},
{'variant':'cost', 'label':'總成本 (Cost)', 'value': '${:,.0f}'.format(kpi.cost), 'icon':'file-invoice-dollar', 'show': cols.cost or cols.profit},
{'variant':'profit', 'label':'毛利額 (Profit)', 'value': '${:,.0f}'.format(kpi.gross_margin), 'icon':'hand-holding-usd', 'show': cols.cost or cols.profit},
{'variant':'rate', 'label':'毛利率 (%)', 'value': '{:.1f}%'.format(kpi.gross_margin_rate),'icon':'percentage', 'show': cols.cost or cols.profit},
{'variant':'qty', 'label':'總銷量 (Qty)', 'value': '{:,.0f}'.format(kpi.qty), 'icon':'boxes', 'show': true},
{'variant':'sku', 'label':'商品數 (SKU)', 'value': '{:,}'.format(kpi.sku_count|default(kpi.count, true)), 'icon':'tags', 'show': true}
] %}
<div class="row mb-4 sa-kpi-row">
{% for k in kpi_cards %}{% if k.show %}
<div class="col-md-2">
<article class="card kpi-card sa-kpi sa-kpi--{{ k.variant }}">
<div class="card-body p-3">
<div class="kpi-label">{{ k.label }}</div>
<div class="kpi-value">{{ k.value }}</div>
<i class="fas fa-{{ k.icon }} icon-bg" aria-hidden="true"></i>
</div>
</article>
</div>
{% endif %}{% endfor %}
</div>
<!-- ============ ABC 分析 (Pareto) ============ -->
{% if abc_stats %}
<section class="row mb-4">
<div class="col-12">
<div class="card sa-panel sa-panel--abc">
<div class="card-body py-3">
<div class="d-flex align-items-center justify-content-between">
<h6 class="sa-panel__title">
<i class="fas fa-sort-amount-down me-2" aria-hidden="true"></i>ABC 分析 (80/20 法則)
</h6>
<span class="sa-tag sa-tag--neutral">點擊類別查看詳情</span>
</div>
<div class="row mt-3 sa-abc">
{% set abc_classes = [
{'k':'A','tier':'p0','title':'A 類 (核心)'},
{'k':'B','tier':'p1','title':'B 類 (次要)'},
{'k':'C','tier':'p2','title':'C 類 (長尾)'}
] %}
{% for c in abc_classes %}
<div class="col-md-4 sa-abc__cell sa-abc__cell--{{ c.tier }}"
onclick="window.open('/abc_analysis/detail?class={{ c.k }}&{{ request.query_string.decode() }}', '_blank')">
<h5 class="sa-abc__heading">{{ c.title }}</h5>
<div class="sa-abc__metric">營收佔比:{{ "{:.1f}%".format(abc_stats[c.k]['pct_rev']) }}</div>
<div class="sa-abc__sku">{{ abc_stats[c.k]['count'] }} SKU ({{ "{:.1f}%".format(abc_stats[c.k]['pct_sku']) }})</div>
<div class="sa-abc__bar">
<div class="sa-abc__bar-fill" style="width: {{ abc_stats[c.k]['pct_rev'] }}%"></div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</section>
{% endif %}
<!-- ============ 年度對比 (YoY) ============ -->
<section class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<header class="sa-yoy-head">
<h6 class="sa-panel__title">
<i class="fas fa-chart-line me-2" aria-hidden="true"></i>年度對比 (YoY Comparison)
</h6>
<div class="sa-yoy-controls">
<select id="yoy-year1" class="form-select form-select-sm" aria-label="基準年">
<option value="2025" selected>2025</option>
<option value="2024">2024</option>
<option value="2023">2023</option>
</select>
<span class="sa-yoy-controls__sep">vs</span>
<select id="yoy-year2" class="form-select form-select-sm" aria-label="對比年">
<option value="2026" selected>2026</option>
<option value="2025">2025</option>
<option value="2024">2024</option>
</select>
<select id="yoy-metric" class="form-select form-select-sm" aria-label="指標">
<option value="revenue" selected>銷售金額</option>
<option value="qty">銷售數量</option>
<option value="profit">毛利金額</option>
</select>
<button class="btn btn-sm btn-outline-primary" onclick="loadYoYData()" title="重新載入">
<i class="fas fa-sync-alt" aria-hidden="true"></i>
</button>
</div>
</header>
<div class="row mb-3 sa-yoy-summary" id="yoy-summary">
<div class="col-md-4">
<div class="card sa-yoy-card">
<div class="card-body text-center py-3">
<small id="yoy-year1-label">2024年</small>
<h4 class="sa-yoy-card__value sa-yoy-card__value--neutral" id="yoy-year1-value">$0</h4>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card sa-yoy-card">
<div class="card-body text-center py-3">
<small id="yoy-year2-label">2025年</small>
<h4 class="sa-yoy-card__value sa-yoy-card__value--accent" id="yoy-year2-value">$0</h4>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card sa-yoy-card sa-yoy-card--growth" id="yoy-growth-card">
<div class="card-body text-center py-3">
<small>成長率</small>
<h4 class="sa-yoy-card__value" id="yoy-growth-value">
<i class="fas fa-arrow-up" aria-hidden="true"></i> 0%
</h4>
</div>
</div>
</div>
</div>
<div class="sa-chart-shell" style="height: 250px;">
<canvas id="yoy-chart"></canvas>
</div>
</div>
</div>
</div>
</section>
<!-- ============ 廠商獲利能力排行 ============ -->
{% if vendor_stats %}
<section class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-industry me-2" aria-hidden="true"></i>廠商獲利能力排行 (Top 100)</span>
<a href="/api/export/excel/vendor?{{ request.query_string.decode() }}" class="btn btn-sm btn-outline-success">
<i class="fas fa-file-excel me-1" aria-hidden="true"></i>匯出報表
</a>
</div>
<div class="card-body">
<div class="table-responsive sa-vendor-table">
<table class="table table-hover table-sm align-middle mb-0">
<thead>
<tr>
<th class="text-center" style="width: 60px;">排名</th>
<th>廠商名稱</th>
<th class="text-end">總業績</th>
<th class="text-end">佔比 (%)</th>
<th class="text-end">總銷量</th>
<th class="text-end">平均客單 (ASP)</th>
{% if cols.cost or cols.profit %}<th class="text-end">毛利額</th>{% endif %}
{% if cols.cost or cols.profit %}<th class="text-end">毛利率</th>{% endif %}
<th class="text-end">商品數 (SKU)</th>
<th class="text-end">平均單品產值</th>
</tr>
</thead>
<tbody>
{% for v in vendor_stats %}
<tr>
<td class="text-center text-muted">{{ loop.index }}</td>
<td class="fw-bold">
<a href="javascript:setFilter('vendor', '{{ v.name }}')" class="sa-vendor-link" title="點擊篩選此廠商商品">
{{ v.name }}
<i class="fas fa-filter ms-1 sa-vendor-link__icon" aria-hidden="true"></i>
</a>
</td>
<td class="text-end">${{ "{:,.0f}".format(v.revenue) }}</td>
<td class="text-end small text-muted">{{ "{:.1f}%".format(v.share) }}</td>
<td class="text-end">{{ "{:,.0f}".format(v.qty) }}</td>
<td class="text-end">${{ "{:,.0f}".format(v.asp) }}</td>
{% if cols.cost or cols.profit %}
<td class="text-end sa-vendor-table__profit">${{ "{:,.0f}".format(v.profit) }}</td>
<td class="text-end">
{% set tier = 'p2' if v.margin_rate >= 30 else ('p1' if v.margin_rate >= 15 else 'p0') %}
<span class="sa-margin-badge sa-margin-badge--{{ tier }}">{{ "{:.1f}%".format(v.margin_rate) }}</span>
</td>
{% endif %}
<td class="text-end">{{ v.sku_count }}</td>
<td class="text-end">${{ "{:,.0f}".format(v.revenue / v.sku_count if v.sku_count > 0 else 0) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
{% endif %}
<!-- ============ 商業洞察 (Business Insights) ============ -->
<section class="row mb-4">
<div class="col-12">
<div class="card sa-insights">
<div class="card-header sa-insights__head">
<i class="fas fa-lightbulb me-2" aria-hidden="true"></i>商業洞察 (Top 3 Highlights)
</div>
<div class="card-body">
<div class="row sa-insights__row">
<!-- 業績 Top 3 -->
<div class="col-md-4 sa-insights__col">
<div class="sa-insights__col-head">
<h6 class="sa-insights__col-title sa-insights__col-title--accent">🏆 業績貢獻王 (Revenue)</h6>
<button class="btn btn-sm btn-outline-primary" onclick="showTopDetail('revenue', 'amount')">
<i class="fas fa-list me-1" aria-hidden="true"></i>詳細
</button>
</div>
<small class="sa-insights__sub">分類 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.rev_cats %}
<li><span class="sa-insights__rank sa-insights__rank--accent">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">${{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
<small class="sa-insights__sub">商品 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.rev_prods %}
<li class="text-truncate"><span class="sa-insights__rank sa-insights__rank--accent">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">${{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
</div>
<!-- 毛利 Top 3 -->
{% if cols.cost or cols.profit %}
<div class="col-md-4 sa-insights__col">
<div class="sa-insights__col-head">
<h6 class="sa-insights__col-title sa-insights__col-title--olive">💰 獲利金雞母 (Gross Margin)</h6>
<button class="btn btn-sm btn-outline-success" onclick="showTopDetail('margin', 'profit')">
<i class="fas fa-list me-1" aria-hidden="true"></i>詳細
</button>
</div>
<small class="sa-insights__sub">分類 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.margin_cats %}
<li><span class="sa-insights__rank sa-insights__rank--olive">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">${{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
<small class="sa-insights__sub">商品 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.margin_prods %}
<li class="text-truncate"><span class="sa-insights__rank sa-insights__rank--olive">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">${{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
</div>
{% else %}
<div class="col-md-4 sa-insights__col sa-insights__col--empty">
<i class="fas fa-info-circle me-2" aria-hidden="true"></i>無成本/毛利資料
</div>
{% endif %}
<!-- 銷量 Top 3 -->
<div class="col-md-4 sa-insights__col sa-insights__col--last">
<div class="sa-insights__col-head">
<h6 class="sa-insights__col-title sa-insights__col-title--mahogany">📦 人氣引流款 (Sales Qty)</h6>
<button class="btn btn-sm btn-outline-primary" onclick="showTopDetail('quantity', 'qty')">
<i class="fas fa-list me-1" aria-hidden="true"></i>詳細
</button>
</div>
<small class="sa-insights__sub">分類 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.qty_cats %}
<li><span class="sa-insights__rank sa-insights__rank--mahogany">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">{{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
<small class="sa-insights__sub">商品 Top 3</small>
<ul class="sa-insights__list">
{% for item in insights.qty_prods %}
<li class="text-truncate"><span class="sa-insights__rank sa-insights__rank--mahogany">{{ loop.index }}</span>{{ item.name }}<span class="sa-insights__value">{{ "{:,.0f}".format(item.value) }}</span></li>
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============ 圖表區塊 ============ -->
{# Turn C圖表 + DataTable配色由 analysis-chart-theme.js 統一注入 #}
<div class="row">
<div class="col-lg-8">
<div class="card">
<div class="card-header"><i class="fas fa-chart-bar me-2" aria-hidden="true"></i>Top 20 熱銷排行 ({{ '銷售金額' if selected_metric == 'amount' else '銷售數量' }})</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 600px;"><canvas id="barChart"></canvas></div></div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header"><i class="fas fa-chart-pie me-2" aria-hidden="true"></i>全站類別分佈 (Top 12)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 400px;"><canvas id="categoryChart"></canvas></div></div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header"><i class="fas fa-th-large me-2" aria-hidden="true"></i>業績板塊分佈 (分類 → 商品)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 400px;"><canvas id="treemapChart"></canvas></div></div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-lg-6">
<div class="card">
<div class="card-header"><i class="fas fa-chart-column me-2" aria-hidden="true"></i>價格帶業績貢獻 (Price Range)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 350px;"><canvas id="priceDistChart"></canvas></div></div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header"><i class="fas fa-braille me-2" aria-hidden="true"></i>價格 vs 銷量分佈 (Scatter Plot)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 350px;"><canvas id="scatterChart"></canvas></div></div>
</div>
</div>
</div>
{% if cols.cost or cols.profit %}
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header"><i class="fas fa-chess-board me-2" aria-hidden="true"></i>商品策略 BCG 矩陣 (波士頓矩陣)</div>
<div class="card-body">
<p class="text-muted small mb-2"><i class="fas fa-info-circle me-1" aria-hidden="true"></i> X軸銷量 (市場份額) | Y軸毛利率 (獲利能力) | 十字線:中位數閾值</p>
<div class="sa-chart-shell" style="height: 500px;"><canvas id="bcgChart"></canvas></div>
</div>
</div>
</div>
</div>
{% endif %}
{% if seasonality_data %}
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header"><i class="fas fa-sun me-2" aria-hidden="true"></i>淡旺季熱力圖 (Seasonality Heatmap) — Top 10 分類</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 400px;"><canvas id="seasonalityChart"></canvas></div></div>
</div>
</div>
</div>
{% endif %}
{% if marketing_data %}
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-bullhorn me-2" aria-hidden="true"></i>行銷活動業績貢獻 (Marketing Campaign Contribution)</span>
<button class="btn btn-sm btn-outline-success" onclick="exportMarketingExcel('all')">
<i class="fas fa-file-excel me-1" aria-hidden="true"></i>匯出全部
</button>
</div>
<div class="card-body">
<div class="row">
<div class="col-lg-6 mb-4">
<h6 class="sa-mkt-title sa-mkt-title--accent"><i class="fas fa-tags me-2" aria-hidden="true"></i>折扣活動排行 (Discount Campaigns)</h6>
<div class="sa-chart-shell" style="height: 350px;"><canvas id="mktDiscountChart"></canvas></div>
</div>
<div class="col-lg-6 mb-4">
<h6 class="sa-mkt-title sa-mkt-title--olive"><i class="fas fa-ticket-alt me-2" aria-hidden="true"></i>折價券活動排行 (Coupon Campaigns)</h6>
<div class="sa-chart-shell" style="height: 350px;"><canvas id="mktCouponChart"></canvas></div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% if cols.date %}
<div class="row mt-4">
<div class="col-lg-6">
<div class="card">
<div class="card-header"><i class="fas fa-calendar-alt me-2" aria-hidden="true"></i>每月業績趨勢 (Monthly Trend)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 300px;"><canvas id="monthlyChart"></canvas></div></div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-header"><i class="fas fa-chart-line me-2" aria-hidden="true"></i>每週業績趨勢 (全年度)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 300px;"><canvas id="weeklyChart"></canvas></div></div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-lg-4">
<div class="card">
<div class="card-header"><i class="fas fa-calendar-week me-2" aria-hidden="true"></i>每日業績趨勢 (週一至週日)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 300px;"><canvas id="dowChart"></canvas></div></div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header"><i class="fas fa-clock me-2" aria-hidden="true"></i>每小時業績熱點 (00:00 — 23:00)</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 300px;"><canvas id="hourlyChart"></canvas></div></div>
</div>
</div>
<div class="col-lg-4">
<div class="card">
<div class="card-header">
<i class="fas fa-th me-2" aria-hidden="true"></i>多維度熱點 (星期 × 小時)
<i class="fas fa-info-circle text-muted ms-1" data-bs-toggle="tooltip" title="此圖表用於識別每週的「黃金銷售時段」。顏色越深代表該時段業績越好。" aria-hidden="true"></i>
</div>
<div class="card-body"><div class="sa-chart-shell" style="height: 300px;"><canvas id="heatmapChart"></canvas></div></div>
</div>
</div>
</div>
{% endif %}
<!-- ============ 詳細資料表 (DataTable) ============ -->
<div class="card mt-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-list-ol me-2" aria-hidden="true"></i>詳細數據列表 (Top 1000)</span>
</div>
<div class="card-body">
<table id="dataTable" class="table table-hover align-middle mb-0" style="width:100%">
<thead>
<tr>
<th class="text-center" style="width: 60px;">排名</th>
<th style="width: 100px;">商品 ID</th>
<th style="width: 25%;">商品名稱</th>
{% if cols.brand %}<th>品牌</th>{% endif %}
{% if cols.vendor %}<th>廠商名稱</th>{% endif %}
{% if cols.cat %}<th>商品館 (分類)</th>{% endif %}
{% if cols.qty %}<th>平均單價</th>{% endif %}
{% if cols.cost or cols.profit %}<th>毛利率</th>{% endif %}
{% if cols.return_qty %}<th>退貨率</th>{% endif %}
{% if cols.date %}<th>銷售月份</th>{% endif %}
{% if cols.qty %}<th class="text-end">銷售數量</th>{% endif %}
<th class="text-end">銷售金額</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
{% endif %}{# /no_filter else #}
</div>{# /sales-analysis-page #}
{% endif %}{# /error #}
{% endblock %}
{% block extra_js %}
{% if not no_filter %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-chart-treemap@2.0.2/dist/chartjs-chart-treemap.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.datatables.net/1.11.5/js/jquery.dataTables.min.js"></script>
<script src="https://cdn.datatables.net/1.11.5/js/dataTables.bootstrap5.min.js"></script>
{% endif %}
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/l10n/zh-tw.js"></script>
<!-- Chart 全站主題(暖色 palette— 必須在所有 chart 初始化之前 -->
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
{% if not error %}
{% set empty_chart_data = {'labels': [], 'values': [], 'chart_values': [], 'metric_label': ''} %}
<script id="sales-data" type="application/json">
{
"barData": {{ bar_data | default(empty_chart_data, true) | tojson | safe }},
"categoryData": {{ category_data | default(cat_data | default(empty_chart_data, true), true) | tojson | safe }},
"treemapData": {{ treemap_data | default([], true) | tojson | safe }},
"monthlyData": {{ monthly_data | default(empty_chart_data, true) | tojson | safe }},
"weeklyData": {{ weekly_data | default(empty_chart_data, true) | tojson | safe }},
"dowData": {{ dow_data | default(empty_chart_data, true) | tojson | safe }},
"hourlyData": {{ hourly_data | default(empty_chart_data, true) | tojson | safe }},
"heatmapData": {{ heatmap_data | default({}, true) | tojson | safe }},
"scatterData": {{ scatter_data | default([], true) | tojson | safe }},
"priceDistData": {{ price_dist_data | default(empty_chart_data, true) | tojson | safe }},
"bcgData": {{ bcg_data | default({}, true) | tojson | safe }},
"seasonalityData": {{ seasonality_data | default({}, true) | tojson | safe }},
"marketingData": {{ marketing_data | default({}, true) | tojson | safe }}
}
</script>
{% endif %}
<!-- Page logicfilters / dropdown search / charts / DataTable / Flatpickr 初始化 -->
<script src="{{ url_for('static', filename='js/page-sales-analysis.js') }}"></script>
{% endblock %}