Files
ewoooc/templates/sales_analysis.html
ogt c1b375f41c
Some checks failed
CD Pipeline / deploy (push) Failing after 6m19s
fix: harden import auth and utility page copy
2026-06-26 06:44:51 +08:00

880 lines
46 KiB
HTML
Raw 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 %}PChome 業績作戰分析 - 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 %}
<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' %}
<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>
PChome 業績作戰分析
</h4>
<p class="sa-page-head__brief">用分類、品牌與毛利找出主推、守價與補資料順序。</p>
{% 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-filter" 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>
<section class="sa-command-strip" aria-label="PChome 業績作戰順序">
<span class="sa-command-strip__label">決策順序</span>
<strong><i class="fas fa-bullhorn" aria-hidden="true"></i>主推高業績</strong>
<strong><i class="fas fa-shield-alt" aria-hidden="true"></i>守住高毛利</strong>
<strong><i class="fas fa-link" aria-hidden="true"></i>補齊價差證據</strong>
<a href="/ai_intelligence" class="sa-command-strip__link">回今日作戰</a>
</section>
{% 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-calendar-week 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">
選好期間後,直接看主推、守毛利與補比價的商品。
</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-calendar-week 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 %}
{% set kpi_cards = [
{'variant':'revenue','label':'總業績', 'value': '${:,.0f}'.format(kpi.revenue), 'icon':'coins', 'show': true},
{'variant':'cost', 'label':'總成本', 'value': '${:,.0f}'.format(kpi.cost), 'icon':'file-invoice-dollar', 'show': cols.cost or cols.profit},
{'variant':'profit', 'label':'毛利額', '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':'總銷量', 'value': '{:,.0f}'.format(kpi.qty), 'icon':'boxes', 'show': true},
{'variant':'sku', 'label':'商品數', '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>
{% 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>主推分層
</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'] }}{{ "{:.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 %}
<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>年度成長對照
</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>廠商毛利與主推優先序</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">平均客單</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">商品數</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 %}
<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
</div>
<div class="card-body">
<div class="row sa-insights__row">
<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"><i class="fas fa-trophy" aria-hidden="true"></i>業績主推</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>
{% 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"><i class="fas fa-coins" aria-hidden="true"></i>高毛利守價</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 %}
<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"><i class="fas fa-fire" aria-hidden="true"></i>人氣引流</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>
<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>全站類別佔比</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>價格帶業績貢獻</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>價格與銷量分佈</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>商品策略矩陣</div>
<div class="card-body">
<p class="text-muted small mb-2"><i class="fas fa-info-circle me-1" aria-hidden="true"></i>右上優先主推;右下先守價或調整組合。</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>淡旺季熱力圖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>活動業績貢獻</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>折扣活動排行</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>折價券活動排行</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>每月業績趨勢</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>每小時業績熱點</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 %}
<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;">商品編號</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 / Flatpickr 初始化 -->
<script src="{{ url_for('static', filename='js/page-sales-analysis.js') }}"></script>
{% endblock %}