565 lines
26 KiB
HTML
565 lines
26 KiB
HTML
{% extends 'ewoooc_base.html' %}
|
||
{% block title %}當日業績看板 - EwoooC{% endblock %}
|
||
|
||
{#
|
||
v3 改寫重點:
|
||
- 移除 622 行 inline <style>,全部抽到 page-daily-sales.css
|
||
- KPI 卡片從 Bootstrap bg-primary/warning/success/info/secondary/purple 漸層
|
||
→ 改用語意化 .kpi-card.kpi--{revenue|cost|profit|sku|aov|qty}
|
||
- 行事曆 selected/dod-up/dod-down 配色改用 token,不再寫死紫紅綠
|
||
- 漲跌色:上漲 → tag-rust(暖紅),下跌 → tag-olive(暖綠橄欖),與 dashboard 一致
|
||
- 圖表配色完全由 page-daily-sales.js 從 CSS var 讀取
|
||
- extends ewoooc_base.html(自動載入 sidebar、tokens、dotmatrix)
|
||
- 用 Jinja macro 收斂 6 張 KPI 卡片重複結構
|
||
#}
|
||
|
||
{% block extra_css %}
|
||
<link rel="stylesheet" href="https://cdn.datatables.net/1.11.5/css/dataTables.bootstrap5.min.css">
|
||
<link rel="stylesheet" href="{{ url_for('static', filename='css/page-daily-sales.css') }}">
|
||
{% endblock %}
|
||
|
||
{# ---- KPI 卡片 macro ---- #}
|
||
{% macro kpi_card(variant, label, icon, current_val, dod_val, wow_val, month_val, month_extra, is_month_view, value_format='${:,.0f}') %}
|
||
<div class="daily-kpi-slot">
|
||
<article class="kpi-card kpi--{{ variant }}">
|
||
<div class="kpi-card__body">
|
||
<div class="kpi-card__label">{{ label }}</div>
|
||
{% if is_month_view %}
|
||
<div class="kpi-card__value">{{ value_format.format(month_val) }}</div>
|
||
<div class="kpi-card__meta">
|
||
<span class="kpi-tag">月度累計</span>
|
||
{% if month_extra %}<span class="kpi-tag kpi-tag--ghost">{{ month_extra }}</span>{% endif %}
|
||
</div>
|
||
{% else %}
|
||
<div class="kpi-card__value">{{ value_format.format(current_val) }}</div>
|
||
<div class="kpi-card__meta">
|
||
<span class="kpi-delta {{ 'is-up' if dod_val >= 0 else 'is-down' }}">
|
||
<span class="kpi-delta__label">DoD</span>
|
||
<i class="fas fa-{{ 'arrow-up' if dod_val >= 0 else 'arrow-down' }}"></i>
|
||
{{ "{:+.1f}%".format(dod_val) }}
|
||
</span>
|
||
<span class="kpi-delta {{ 'is-up' if wow_val >= 0 else 'is-down' }}">
|
||
<span class="kpi-delta__label">WoW</span>
|
||
<i class="fas fa-{{ 'arrow-up' if wow_val >= 0 else 'arrow-down' }}"></i>
|
||
{{ "{:+.1f}%".format(wow_val) }}
|
||
</span>
|
||
</div>
|
||
{% endif %}
|
||
<i class="fas fa-{{ icon }} kpi-card__icon-bg" aria-hidden="true"></i>
|
||
</div>
|
||
</article>
|
||
</div>
|
||
{% endmacro %}
|
||
|
||
{% macro chart_snapshot(labels, values, mode='currency', limit=12) %}
|
||
{% set total = labels|length %}
|
||
<div class="chart-fallback-list">
|
||
{% for label in labels %}
|
||
{% if loop.index > total - limit %}
|
||
{% set val = values[loop.index0]|default(0) %}
|
||
<span class="chart-fallback-item">
|
||
<b>{{ label }}</b>
|
||
<strong>
|
||
{% if mode == 'pct' %}{{ "{:+.1f}%".format(val) }}
|
||
{% elif mode == 'number' %}{{ "{:,.0f}".format(val) }}
|
||
{% else %}${{ "{:,.0f}".format(val) }}{% endif %}
|
||
</strong>
|
||
</span>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</div>
|
||
{% endmacro %}
|
||
|
||
{% block ewooo_content %}
|
||
<div
|
||
class="daily-sales-page"
|
||
data-screen-label="daily_sales"
|
||
data-selected-date="{{ selected_date }}"
|
||
data-is-month-view="{{ 'true' if is_month_view else 'false' }}">
|
||
{% include 'components/_analysis_report_tabs.html' ignore missing %}
|
||
|
||
{% if error %}
|
||
<div class="empty-state">
|
||
<i class="fas fa-exclamation-triangle empty-state__icon" aria-hidden="true"></i>
|
||
<h4 class="empty-state__title">{{ error }}</h4>
|
||
<p class="empty-state__hint">先到 <a href="/system_settings">系統設定</a> 匯入當日業績,才能判斷下滑與價差壓力。</p>
|
||
</div>
|
||
{% else %}
|
||
|
||
<!-- ============ Hero / Date Selector ============ -->
|
||
<section class="daily-hero">
|
||
<div class="daily-hero__lead">
|
||
<h1 class="daily-hero__title">
|
||
<i class="fas fa-calendar-day" aria-hidden="true"></i>
|
||
當日業績看板
|
||
</h1>
|
||
<p class="daily-hero__subtitle">
|
||
{% if is_month_view %}{{ calendar_data.month_name }}:找出下滑與價差壓力{% else %}{{ selected_date }}:找出下滑與價差壓力{% endif %}
|
||
</p>
|
||
</div>
|
||
<div class="daily-hero__controls">
|
||
<label for="dateSelector" class="daily-hero__label">日期</label>
|
||
<select id="dateSelector" class="form-select form-select-sm" data-daily-date-selector>
|
||
{% for date in available_dates %}
|
||
<option value="{{ date }}" {% if date == selected_date %}selected{% endif %}>{{ date }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ============ Mode / KPI Summary ============ -->
|
||
<section class="daily-mode-banner">
|
||
{% if is_month_view %}
|
||
<span class="badge-mode badge-mode--month"><i class="fas fa-calendar-alt"></i> 月度總計</span>
|
||
<span class="daily-mode-banner__hint">{{ calendar_data.month_name }}</span>
|
||
{% if month_kpi %}<span class="badge-mode badge-mode--ghost">累計 {{ month_kpi.days_with_data }} 天</span>{% endif %}
|
||
{% else %}
|
||
<span class="badge-mode badge-mode--single"><i class="fas fa-calendar-day"></i> 單日</span>
|
||
<span class="daily-mode-banner__hint">{{ selected_date }}</span>
|
||
<a href="/daily_sales" class="daily-mode-banner__link" data-daily-action="month-view">
|
||
<i class="fas fa-chart-line"></i> 月度總計
|
||
</a>
|
||
{% endif %}
|
||
</section>
|
||
|
||
<section class="daily-kpi-grid" aria-label="業績摘要">
|
||
{{ kpi_card(
|
||
'revenue', '總業績', 'chart-line',
|
||
current.total_revenue, dod.total_revenue, wow.total_revenue,
|
||
month_kpi.total_revenue if month_kpi else 0,
|
||
None, is_month_view
|
||
) }}
|
||
{{ kpi_card(
|
||
'cost', '總成本', 'dollar-sign',
|
||
current.total_cost, dod.total_cost, wow.total_cost,
|
||
month_kpi.total_cost if month_kpi else 0,
|
||
None, is_month_view
|
||
) }}
|
||
{{ kpi_card(
|
||
'profit', '毛利', 'hand-holding-usd',
|
||
current.gross_margin, dod.gross_margin, wow.gross_margin,
|
||
month_kpi.gross_margin if month_kpi else 0,
|
||
('毛利率 ' ~ "{:.1f}%".format(month_kpi.margin_rate)) if month_kpi else None,
|
||
is_month_view
|
||
) }}
|
||
{{ kpi_card(
|
||
'sku', 'SKU 數', 'cubes',
|
||
current.sku_count, dod.sku_count, wow.sku_count,
|
||
month_kpi.sku_count if month_kpi else 0,
|
||
'不重複商品' if is_month_view else None,
|
||
is_month_view, value_format='{:,.0f}'
|
||
) }}
|
||
{{ kpi_card(
|
||
'aov', '客單價', 'shopping-cart',
|
||
current.avg_price, dod.avg_price, wow.avg_price,
|
||
month_kpi.avg_price if month_kpi else 0,
|
||
None, is_month_view
|
||
) }}
|
||
{{ kpi_card(
|
||
'qty', '總銷量', 'boxes',
|
||
current.total_qty, dod.total_qty, wow.total_qty,
|
||
month_kpi.total_qty if month_kpi else 0,
|
||
None, is_month_view, value_format='{:,.0f}'
|
||
) }}
|
||
</section>
|
||
|
||
<!-- ============ Calendar ============ -->
|
||
{% if calendar_data %}
|
||
<section class="daily-calendar">
|
||
<header class="daily-calendar__header">
|
||
<h2 class="daily-calendar__title" role="button" tabindex="0" data-daily-action="month-view">
|
||
<i class="fas fa-calendar-alt" aria-hidden="true"></i>
|
||
{{ calendar_data.month_name }} 業績行事曆
|
||
</h2>
|
||
<nav class="daily-calendar__nav" aria-label="月份切換">
|
||
<button type="button" class="btn-month" data-daily-month="{{ calendar_data.prev_month }}">
|
||
<i class="fas fa-chevron-left" aria-hidden="true"></i>上個月
|
||
</button>
|
||
<button type="button" class="btn-month" data-daily-month="{{ calendar_data.next_month }}">
|
||
下個月<i class="fas fa-chevron-right" aria-hidden="true"></i>
|
||
</button>
|
||
</nav>
|
||
</header>
|
||
|
||
<div class="daily-calendar__grid">
|
||
{% for w in ['週一','週二','週三','週四','週五','週六','週日'] %}
|
||
<div class="daily-calendar__weekday">{{ w }}</div>
|
||
{% endfor %}
|
||
|
||
{% for week in calendar_data.weeks %}
|
||
{% for day in week %}
|
||
<div
|
||
class="cal-day
|
||
{% if day.has_data %}has-data dod-{{ day.dod_direction }}{% endif %}
|
||
{% if not day.is_current_month %}is-other-month{% endif %}
|
||
{% if day.is_weekend %}is-weekend{% endif %}
|
||
{% if day.is_holiday %}is-holiday{% endif %}
|
||
{% if day.date == selected_date and not is_month_view %}is-selected{% endif %}"
|
||
data-date="{{ day.date }}"
|
||
data-has-data="{{ 'true' if day.has_data and day.is_current_month else 'false' }}"
|
||
title="{% if day.is_holiday %}🎊 {{ day.holiday_name | e }} | {% endif %}{{ day.weekday | e }}{% if day.has_data %} | 業績 ${{ '{:,.0f}'.format(day.revenue) }} | 毛利 ${{ '{:,.0f}'.format(day.profit) }} | DoD {{ day.dod_percent | e }}%{% endif %}">
|
||
|
||
<div class="cal-day__head">
|
||
<span class="cal-day__num">{{ day.day }}</span>
|
||
{% if day.is_holiday %}<span class="cal-day__holiday">{{ day.holiday_name }}</span>{% endif %}
|
||
</div>
|
||
|
||
{% if day.has_data %}
|
||
<span class="cal-day__badge">
|
||
{% if day.dod_direction == 'up' %}<i class="fas fa-arrow-up" aria-hidden="true"></i>
|
||
{% elif day.dod_direction == 'down' %}<i class="fas fa-arrow-down" aria-hidden="true"></i>
|
||
{% endif %}
|
||
{{ day.dod_percent }}%
|
||
</span>
|
||
<div class="cal-day__metric">
|
||
<span class="cal-day__metric-label">業績</span>
|
||
<strong>${{ '{:,.0f}'.format(day.revenue) }}</strong>
|
||
</div>
|
||
<div class="cal-day__metric cal-day__metric--sub">
|
||
<span class="cal-day__metric-label">毛利</span>
|
||
<strong>${{ '{:,.0f}'.format(day.profit) }}</strong>
|
||
<span>{{ '{:.1f}%'.format(day.margin_rate) }}</span>
|
||
</div>
|
||
<div class="cal-day__chips">
|
||
<span>SKU {{ '{:,.0f}'.format(day.sku_count) }}</span>
|
||
<span>客單 ${{ '{:,.0f}'.format(day.avg_price) }}</span>
|
||
<span>銷量 {{ '{:,.0f}'.format(day.qty) }}</span>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
{% endfor %}
|
||
{% endfor %}
|
||
</div>
|
||
|
||
<div class="daily-calendar-mobile-list" aria-label="手機版業績日期清單">
|
||
{% set mobile_days = namespace(count=0) %}
|
||
{% for week in calendar_data.weeks %}
|
||
{% for day in week %}
|
||
{% if day.has_data and day.is_current_month %}
|
||
{% set mobile_days.count = mobile_days.count + 1 %}
|
||
<a
|
||
class="daily-mobile-day {% if day.date == selected_date and not is_month_view %}is-selected{% endif %}"
|
||
href="/daily_sales?date={{ day.date }}"
|
||
aria-label="{{ day.date }} 業績 {{ '{:,.0f}'.format(day.revenue) }}">
|
||
<span class="daily-mobile-day__date">
|
||
<strong>{{ day.day }}</strong>
|
||
<span>{{ day.weekday }}{% if day.is_holiday %} · {{ day.holiday_name }}{% endif %}</span>
|
||
</span>
|
||
<span class="daily-mobile-day__trend dod-{{ day.dod_direction }}">
|
||
{% if day.dod_direction == 'up' %}<i class="fas fa-arrow-up" aria-hidden="true"></i>
|
||
{% elif day.dod_direction == 'down' %}<i class="fas fa-arrow-down" aria-hidden="true"></i>
|
||
{% endif %}
|
||
{{ day.dod_percent }}%
|
||
</span>
|
||
<span class="daily-mobile-day__metric">
|
||
<span>業績</span>
|
||
<strong>${{ '{:,.0f}'.format(day.revenue) }}</strong>
|
||
</span>
|
||
<span class="daily-mobile-day__metric">
|
||
<span>毛利</span>
|
||
<strong>${{ '{:,.0f}'.format(day.profit) }}</strong>
|
||
<em>{{ '{:.1f}%'.format(day.margin_rate) }}</em>
|
||
</span>
|
||
<span class="daily-mobile-day__chips">
|
||
<span>SKU {{ '{:,.0f}'.format(day.sku_count) }}</span>
|
||
<span>客單 ${{ '{:,.0f}'.format(day.avg_price) }}</span>
|
||
<span>銷量 {{ '{:,.0f}'.format(day.qty) }}</span>
|
||
</span>
|
||
</a>
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endfor %}
|
||
{% if mobile_days.count == 0 %}
|
||
<div class="daily-mobile-empty">本月尚無可檢視的業績日期。</div>
|
||
{% endif %}
|
||
</div>
|
||
</section>
|
||
{% endif %}
|
||
|
||
<!-- ============ Charts Row 1 ============ -->
|
||
<div class="row mb-4">
|
||
<div class="col-lg-8">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-chart-area"></i> 每日業績趨勢(近 30 天)</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="trendChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.revenue, 'currency') }}</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-percentage"></i> 日成長率 (DoD %)</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="dodChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.dod_revenue, 'pct') }}</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ Charts Row 2 ============ -->
|
||
<div class="row mb-4">
|
||
<div class="col-lg-8">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-chart-bar"></i> 週成長對比 (WoW)</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="wowChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.wow_revenue, 'pct') }}</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-trophy"></i> 商品 Top 10</div>
|
||
<div class="card-body">
|
||
<div class="chart-mobile-hint d-md-none">
|
||
<i class="fas fa-hand-pointer"></i> 左右滑動看業績趨勢
|
||
</div>
|
||
<div class="chart-responsive">
|
||
<div class="chart-container has-html-chart" id="top10ChartContainer">
|
||
<canvas id="top10Chart"></canvas>
|
||
{{ chart_snapshot(chart_data.top10_labels, chart_data.top10_values, 'currency', 10) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ Restored / Decision Charts ============ -->
|
||
<div class="row mb-4">
|
||
<div class="col-xl-4 col-lg-6 mb-4 mb-xl-0">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-percent"></i> 毛利率趨勢</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="marginChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.margin_rate, 'pct') }}</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-4 col-lg-6 mb-4 mb-xl-0">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-receipt"></i> 客單價 × 銷量</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="avgQtyChart"></canvas>{{ chart_snapshot(chart_data.labels, chart_data.avg_price, 'currency') }}</div></div>
|
||
</div>
|
||
</div>
|
||
<div class="col-xl-4 col-lg-12">
|
||
<div class="card chart-card">
|
||
<div class="card-header"><i class="fas fa-layer-group"></i> 分類業績 Top 12</div>
|
||
<div class="card-body"><div class="chart-container has-html-chart"><canvas id="categoryRevenueChart"></canvas>{{ chart_snapshot(category_chart.labels if category_chart else [], category_chart.revenue if category_chart else [], 'currency', 12) }}</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ Marketing ============ -->
|
||
{% if competitor_intel %}
|
||
{% set comp_trend = competitor_intel.trend %}
|
||
{% set comp_coverage = competitor_intel.coverage %}
|
||
<div class="row mb-4">
|
||
<div class="col-lg-8">
|
||
<div class="card chart-card">
|
||
<div class="card-header">
|
||
<i class="fas fa-scale-balanced"></i> PChome 價差壓力(近 30 天)
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="chart-container has-html-chart">
|
||
<canvas id="competitorGapChart"></canvas>
|
||
{{ chart_snapshot(comp_trend.labels, comp_trend.avg_gap_pct, 'pct') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-lg-4">
|
||
<div class="card chart-card">
|
||
<header class="card-header card-header--split">
|
||
<span><i class="fas fa-triangle-exclamation"></i> 比價決策覆蓋</span>
|
||
<a class="btn btn-sm btn-table-toggle" href="{{ url_for('dashboard.index', filter='pchome_review', review_status='all', sort_by='pchome_review', order='desc') }}">
|
||
<i class="fas fa-arrow-up-right-from-square"></i> 比價工作台
|
||
</a>
|
||
</header>
|
||
<div class="card-body">
|
||
<div class="chart-container chart-container--sm has-html-chart">
|
||
<canvas id="competitorCoverageChart"></canvas>
|
||
</div>
|
||
<div class="daily-competitor-summary">
|
||
<div>
|
||
<span>決策支援覆蓋率</span>
|
||
<strong class="momo-mono">{{ comp_coverage.decision_support_rate | default(comp_coverage.decision_ready_rate | default(0)) }}%</strong>
|
||
</div>
|
||
<div>
|
||
<span>有效身份配對</span>
|
||
<strong class="momo-mono">{{ comp_coverage.valid_matches | default(0) | number_format }}</strong>
|
||
</div>
|
||
<div>
|
||
<span>精準可告警覆蓋</span>
|
||
<strong class="momo-mono">{{ comp_coverage.decision_ready_rate | default(0) }}%</strong>
|
||
</div>
|
||
<div>
|
||
<span>待刷新</span>
|
||
<strong class="momo-mono">{{ comp_coverage.stale_matches | default(0) | number_format }}</strong>
|
||
</div>
|
||
<div>
|
||
<span>未知新鮮度</span>
|
||
<strong class="momo-mono">{{ comp_coverage.unknown_freshness_matches | default(0) | number_format }}</strong>
|
||
</div>
|
||
<div>
|
||
<span>需單位價覆核</span>
|
||
<strong class="momo-mono">{{ comp_coverage.unit_comparable_count | default(0) | number_format }}</strong>
|
||
</div>
|
||
<div>
|
||
<span>重算待人工覆核</span>
|
||
<strong class="momo-mono">{{ comp_coverage.rescore_accepted_count | default(0) | number_format }}</strong>
|
||
</div>
|
||
<div>
|
||
<span>型錄/任選可比</span>
|
||
<strong class="momo-mono">{{ comp_coverage.catalog_comparable_count | default(0) | number_format }}</strong>
|
||
</div>
|
||
</div>
|
||
<div class="daily-competitor-closure momo-mono" aria-label="人工比價閉環">
|
||
<span>人工採用 {{ comp_coverage.manual_accept_count | default(0) | number_format }}</span>
|
||
<span>人工否決 {{ comp_coverage.manual_reject_count | default(0) | number_format }}</span>
|
||
<span>人工單位價 {{ comp_coverage.manual_unit_price_count | default(0) | number_format }}</span>
|
||
</div>
|
||
{% if competitor_intel.review_queue %}
|
||
<ol class="daily-competitor-risk-list daily-competitor-risk-list--review">
|
||
{% for item in competitor_intel.review_queue[:3] %}
|
||
<li>
|
||
<span>{{ item.name[:22] }} · {{ item.status_label }}</span>
|
||
<strong class="momo-mono">{{ item.action_label }}</strong>
|
||
{% if item.unit_comparison and item.unit_comparison.summary %}
|
||
<em>{{ item.unit_comparison.summary }}</em>
|
||
{% endif %}
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
{% elif competitor_intel.top_risks %}
|
||
<ol class="daily-competitor-risk-list">
|
||
{% for item in competitor_intel.top_risks[:5] %}
|
||
<li>
|
||
<span>{{ item.name[:24] }}</span>
|
||
<strong class="momo-mono">+{{ item.gap_pct | round(1) }}%</strong>
|
||
</li>
|
||
{% endfor %}
|
||
</ol>
|
||
{% else %}
|
||
<div class="chart-empty"><i class="fas fa-check-circle"></i><p>目前沒有高信心 MOMO 低價壓力商品。</p></div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- ============ Marketing ============ -->
|
||
{% if marketing_data %}
|
||
<div class="row mb-4">
|
||
<div class="col-12">
|
||
<div class="card chart-card">
|
||
<header class="card-header card-header--split">
|
||
<span><i class="fas fa-bullhorn"></i> 行銷活動業績貢獻</span>
|
||
<button type="button" class="btn btn-sm btn-export" data-daily-export="marketing">
|
||
<i class="fas fa-file-excel"></i> 匯出 Excel
|
||
</button>
|
||
</header>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-lg-6 mb-4">
|
||
<h6 class="marketing-subhead"><i class="fas fa-tags"></i> 折扣活動 Top 10</h6>
|
||
{% if marketing_data.discount %}
|
||
<div class="chart-container chart-container--md has-html-chart"><canvas id="discountChart"></canvas>{{ chart_snapshot(marketing_data.discount['labels'], marketing_data.discount['values'], 'currency', 10) }}</div>
|
||
{% else %}
|
||
<div class="chart-empty"><i class="fas fa-info-circle"></i><p>暫無折扣活動數據</p></div>
|
||
{% endif %}
|
||
</div>
|
||
<div class="col-lg-6 mb-4">
|
||
<h6 class="marketing-subhead"><i class="fas fa-ticket-alt"></i> 折價券活動 Top 10</h6>
|
||
{% if marketing_data.coupon %}
|
||
<div class="chart-container chart-container--md has-html-chart"><canvas id="couponChart"></canvas>{{ chart_snapshot(marketing_data.coupon['labels'], marketing_data.coupon['values'], 'currency', 10) }}</div>
|
||
{% else %}
|
||
<div class="chart-empty"><i class="fas fa-info-circle"></i><p>暫無折價券活動數據</p></div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- ============ Category Table ============ -->
|
||
{% set category_total = category_total_count|default(categories|length if categories else 0) %}
|
||
{% set category_visible = category_visible_count|default(categories|length if categories else 0) %}
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card chart-card">
|
||
<header class="card-header card-header--split">
|
||
<span>
|
||
<i class="fas fa-table"></i> 分類業績明細
|
||
<small class="daily-table-count momo-mono">顯示 {{ category_visible }} / {{ category_total }} 筆</small>
|
||
</span>
|
||
<span class="daily-table-actions">
|
||
{% if category_show_all|default(false) and category_total > category_table_limit|default(120) %}
|
||
<a class="btn btn-sm btn-table-toggle" href="{{ category_limited_url }}">收合前 {{ category_table_limit|default(120) }} 筆</a>
|
||
{% elif category_total > category_visible %}
|
||
<a class="btn btn-sm btn-table-toggle" href="{{ category_show_all_url }}">顯示全部</a>
|
||
{% endif %}
|
||
<button type="button" class="btn btn-sm btn-export" data-daily-export="category">
|
||
<i class="fas fa-file-excel"></i> 匯出 Excel
|
||
</button>
|
||
</span>
|
||
</header>
|
||
<div class="card-body">
|
||
<div class="chart-mobile-hint d-md-none">
|
||
<i class="fas fa-hand-pointer"></i> 左右滑動看分類明細
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table id="categoryTable" class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
{% for col in ['分類','廠商','總業績','總成本','毛利','毛利率','總銷量','SKU 數','平均單價'] %}
|
||
<th>{{ col }}</th>
|
||
{% endfor %}
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for cat in categories %}
|
||
<tr>
|
||
<td data-label="分類">{{ cat.category }}</td>
|
||
<td data-label="廠商">{{ cat.vendor or '-' }}</td>
|
||
<td class="num" data-label="總業績">${{ "{:,.0f}".format(cat.revenue) }}</td>
|
||
<td class="num" data-label="總成本">${{ "{:,.0f}".format(cat.cost or 0) }}</td>
|
||
<td class="num" data-label="毛利">${{ "{:,.0f}".format(cat.profit or 0) }}</td>
|
||
<td class="num" data-label="毛利率">{{ "{:.1f}%".format(cat.margin_rate) }}</td>
|
||
<td class="num" data-label="總銷量">{{ "{:,.0f}".format(cat.qty or 0) }}</td>
|
||
<td class="num" data-label="SKU 數">{{ cat.sku_count or 0 }}</td>
|
||
<td class="num" data-label="平均單價">${{ "{:,.0f}".format(cat.avg_price) }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% endif %}
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<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>
|
||
<script src="{{ url_for('static', filename='js/analysis-chart-theme.js') }}"></script>
|
||
{% if not error %}
|
||
{% set daily_sales_payload = {
|
||
'chartData': chart_data,
|
||
'competitor': competitor_intel,
|
||
'categoryChart': category_chart,
|
||
'marketing': {
|
||
'discount': {
|
||
'labels': marketing_data.discount | map(attribute='name') | list,
|
||
'values': marketing_data.discount | map(attribute='revenue') | list
|
||
} if marketing_data and marketing_data.discount else none,
|
||
'coupon': {
|
||
'labels': marketing_data.coupon | map(attribute='name') | list,
|
||
'values': marketing_data.coupon | map(attribute='revenue') | list
|
||
} if marketing_data and marketing_data.coupon else none
|
||
},
|
||
'isMonthView': is_month_view
|
||
} %}
|
||
<template id="daily-sales-data">{{ daily_sales_payload | tojson }}</template>
|
||
<script src="{{ url_for('static', filename='js/page-daily-sales.js') }}"></script>
|
||
{% endif %}
|
||
{% endblock %}
|