<!--数据统计 - 井盖设备智能终端运维看板-->
<template>
  <div class="terminal-operation-dashboard">
    <!-- 页面头部 -->
    <div class="page-header">
      <div class="header-left">
        <el-icon><Monitor /></el-icon>
        <span class="title">井盖设备智能终端运维看板</span>
        <el-tag type="danger" effect="dark">异常终端 {{ kpi.abnormal }}</el-tag>
      </div>
      <div class="header-right">
        <el-date-picker
          v-model="dateRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          style="width: 260px"
          size="default"
          @change="loadAll"
        />
        <el-button type="primary" :icon="Refresh" @click="loadAll" :loading="loading">刷新</el-button>
      </div>
    </div>

    <!-- KPI 指标卡片 -->
    <div class="kpi-cards">
      <div class="kpi-card">
        <div class="kpi-value">{{ kpi.total }}</div>
        <div class="kpi-label">终端总数</div>
      </div>
      <div class="kpi-card warning">
        <div class="kpi-value">{{ kpi.abnormal }}</div>
        <div class="kpi-label">异常终端</div>
        <div class="kpi-trend">占比 {{ kpi.abnormalRate }}%</div>
      </div>
      <div class="kpi-card success">
        <div class="kpi-value">{{ kpi.solvedRate }}%</div>
        <div class="kpi-label">问题解决率</div>
        <div class="kpi-trend">已派发 {{ kpi.submittedCount }} / 异常 {{ kpi.abnormal }}</div>
      </div>
      <div class="kpi-card">
        <div class="kpi-value">{{ kpi.disposalRate }}%</div>
        <div class="kpi-label">案件处置率</div>
        <div class="kpi-trend">工单总数 {{ kpi.totalCase }}</div>
      </div>
      <div class="kpi-card">
        <div class="kpi-value">{{ kpi.delayRate }}%</div>
        <div class="kpi-label">延期率</div>
        <div class="kpi-trend" :class="{ 'trend-down': Number(kpi.delayRate) > 0 }">统计范围内</div>
      </div>
      <div class="kpi-card">
        <div class="kpi-value">{{ kpi.avgResponseTime }}</div>
        <div class="kpi-label">平均响应时长(小时)</div>
      </div>
    </div>

    <!-- 图表区域 -->
    <div class="charts-row">
      <div class="chart-card">
        <div class="chart-header">
          <span>异常终端数量趋势（月度）</span>
        </div>
        <div ref="trendChartRef" class="chart-container" v-loading="loading"></div>
      </div>
      <div class="chart-card">
        <div class="chart-header">
          <span>问题类型分布</span>
        </div>
        <div ref="problemTypeChartRef" class="chart-container" v-loading="loading"></div>
      </div>
    </div>

    <div class="charts-row">
      <div class="chart-card">
        <div class="chart-header">
          <span>处置率 vs 延期率趋势</span>
        </div>
        <div ref="disposalTrendChartRef" class="chart-container" v-loading="loading"></div>
      </div>
      <div class="chart-card">
        <div class="chart-header">
          <span>区域运维情况排行</span>
        </div>
        <div ref="regionRankChartRef" class="chart-container" v-loading="loading"></div>
      </div>
    </div>

    <!-- 异常终端列表 -->
    <div class="device-list-container">
      <div class="list-header">
        <span><el-icon><List /></el-icon> 异常终端列表（共 {{ filteredAbnormalDevices.length }} 台）</span>
        <div class="list-filters">
          <el-input
            v-model="searchKeyword"
            placeholder="搜索设备编号/名称/位置"
            clearable
            :prefix-icon="Search"
            style="width: 200px"
            size="small"
          />
          <el-select v-model="faultTypeFilter" placeholder="故障类型" clearable size="small" style="width: 130px">
            <el-option label="倾斜超标" value="tilt" />
            <el-option label="水位超限" value="waterLevel" />
            <el-option label="水浸报警" value="waterInfiltration" />
            <el-option label="通讯故障" value="offline" />
            <el-option label="电池低电量" value="battery" />
            <el-option label="信号弱" value="signal" />
          </el-select>
          <el-button type="primary" size="small" @click="currentPage = 1">查询</el-button>
        </div>
      </div>
      <el-table :data="paginatedAbnormalDevices" stripe border style="width: 100%" v-loading="loading">
        <el-table-column type="index" label="序号" width="55" align="center" />
        <el-table-column prop="code" label="设备编号" width="140" sortable />
        <el-table-column prop="name" label="设备名称" width="140" show-overflow-tooltip />
        <el-table-column prop="deviceTypeName" label="设备类型" width="100" />
        <el-table-column label="故障类型" width="110">
          <template #default="{ row }">
            <el-tag v-for="ft in row.faultTypes" :key="ft.type" :type="getFaultTagType(ft.type)" size="small" style="margin: 1px">{{ ft.name }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="faultDesc" label="故障描述" min-width="180" show-overflow-tooltip />
        <el-table-column prop="locationShort" label="所在位置" min-width="140" show-overflow-tooltip />
        <el-table-column prop="abnormalTime" label="故障时间" width="150" sortable />
        <el-table-column label="处理状态" width="100" align="center">
          <template #default="{ row }">
            <el-tag :type="row.workOrderSubmitted ? 'success' : 'danger'" size="small">
              {{ row.workOrderSubmitted ? '已派发' : '待派发' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="elapsedHours" label="已耗时(h)" width="100" sortable align="center">
          <template #default="{ row }">
            <span :class="{ 'over-time': row.elapsedHours > 48 }">{{ row.elapsedHours }}</span>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="100" fixed="right" align="center">
          <template #default="{ row }">
            <el-button link type="primary" size="small" @click="viewDetail(row)">详情</el-button>
          </template>
        </el-table-column>
      </el-table>
      <div class="pagination-wrapper">
        <el-pagination
          background
          layout="total, sizes, prev, pager, next"
          :total="filteredAbnormalDevices.length"
          v-model:current-page="currentPage"
          v-model:page-size="pageSize"
          :page-sizes="[10, 20, 50]"
        />
      </div>
    </div>

    <!-- 终端详情抽屉 -->
    <el-drawer v-model="detailDrawerVisible" :title="`终端详情 - ${currentDevice?.code}`" size="450px" direction="rtl">
      <div v-if="currentDevice" class="device-detail">
        <el-descriptions :column="1" border>
          <el-descriptions-item label="设备编号">{{ currentDevice.code }}</el-descriptions-item>
          <el-descriptions-item label="设备名称">{{ currentDevice.name }}</el-descriptions-item>
          <el-descriptions-item label="设备类型">{{ currentDevice.deviceTypeName }}</el-descriptions-item>
          <el-descriptions-item label="故障描述">{{ currentDevice.faultDesc }}</el-descriptions-item>
          <el-descriptions-item label="故障时间">{{ currentDevice.abnormalTime }}</el-descriptions-item>
          <el-descriptions-item label="详细地址">{{ currentDevice.location }}</el-descriptions-item>
          <el-descriptions-item label="处理状态">
            <el-tag :type="currentDevice.workOrderSubmitted ? 'success' : 'danger'">
              {{ currentDevice.workOrderSubmitted ? '已派发' : '待派发' }}
            </el-tag>
          </el-descriptions-item>
        </el-descriptions>

        <!-- 监测数据 -->
        <div class="sub-title">监测数据</div>
        <div class="monitor-grid">
          <div class="monitor-item" :class="{ alert: currentDevice.status.tilt }">
            <div class="m-label">倾斜角度</div>
            <div class="m-value">{{ currentDevice.tiltAngle }}°</div>
            <div class="m-extra">阈值: {{ currentDevice.angleThreshold }}°</div>
          </div>
          <div class="monitor-item" :class="{ alert: currentDevice.status.waterInfiltration }">
            <div class="m-label">水浸</div>
            <div class="m-value">{{ currentDevice.status.waterInfiltration ? '报警' : '正常' }}</div>
          </div>
          <div class="monitor-item" :class="{ alert: currentDevice.status.waterLevel }">
            <div class="m-label">水位</div>
            <div class="m-value">{{ currentDevice.status.waterLevel ? '超标' : '正常' }}</div>
          </div>
          <div class="monitor-item" :class="{ alert: currentDevice.status.lowBattery }">
            <div class="m-label">电量</div>
            <div class="m-value">{{ currentDevice.battery }}%</div>
          </div>
          <div class="monitor-item" :class="{ alert: currentDevice.status.lowSignal }">
            <div class="m-label">信号</div>
            <div class="m-value">{{ currentDevice.signal }}%</div>
          </div>
          <div class="monitor-item">
            <div class="m-label">温度</div>
            <div class="m-value">{{ currentDevice.temperature || '—' }}°C</div>
          </div>
        </div>
      </div>
    </el-drawer>
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { Monitor, Refresh, List, Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import * as echarts from 'echarts'
import {
  getManholeMaintenanceDashboard
} from '@/api/pipeNetwork/basic'

// ==================== 常量 ====================
const TYPE_MAP = { '5': '供水窨井', '9': '排水窨井', '10': '燃气窨井' }
const LOW_BATTERY_THRESHOLD = 15
const LOW_SIGNAL_THRESHOLD = 20

// ==================== 状态 ====================
const loading = ref(false)
const dateRange = ref([])
const searchKeyword = ref('')
const faultTypeFilter = ref('')
const currentPage = ref(1)
const pageSize = ref(10)

const detailDrawerVisible = ref(false)
const currentDevice = ref(null)

const kpi = reactive({
  total: 0, abnormal: 0, abnormalRate: '0.0',
  solvedRate: 0, submittedCount: 0, totalCase: 0,
  disposalRate: 0, delayRate: 0,
  avgResponseTime: '—'
})

const abnormalDevices = ref([])

// 图表引用
const trendChartRef = ref(null)
const problemTypeChartRef = ref(null)
const disposalTrendChartRef = ref(null)
const regionRankChartRef = ref(null)
let trendChart = null, problemTypeChart = null, disposalTrendChart = null, regionRankChart = null

// ==================== 日期工具 ====================
function formatDate(date) {
  const y = date.getFullYear()
  const m = String(date.getMonth() + 1).padStart(2, '0')
  const d = String(date.getDate()).padStart(2, '0')
  return `${y}-${m}-${d}`
}

// ==================== 设备数据规范化 ====================
function normalizeDevice(raw) {
  const md = raw?.manholeData || {}
  const st = raw?.equipmentStatus || {}
  const typeId = raw?.equipmentTypeId || ''

  const tiltAngle = parseFloat(md.tiltAngle) || 0
  const angleThreshold = parseFloat(md.angleAlarmThreshold) || 15
  const battery = parseFloat(md.batteryLevel) || 0
  const signal = parseFloat(md.signalStrength) || 0
  const temperature = md.temperatureValue ? parseFloat(md.temperatureValue).toFixed(1) : null
  const isOnline = Number(st.onlineStatus) === 1

  const tilt = isOnline && tiltAngle > angleThreshold
  const waterInfiltration = isOnline && `${md.waterInfiltrationAlarmStatus}` === '1'
  const waterLevel = isOnline && `${md.waterLevelAlarmStatus}` === '1'
  const lowBattery = battery > 0 && battery <= LOW_BATTERY_THRESHOLD
  const lowSignal = signal > 0 && signal <= LOW_SIGNAL_THRESHOLD
  const offline = !isOnline

  const faultTypes = []
  if (tilt) faultTypes.push({ type: 'tilt', name: '倾斜超标' })
  if (waterLevel) faultTypes.push({ type: 'waterLevel', name: '水位超限' })
  if (waterInfiltration) faultTypes.push({ type: 'waterInfiltration', name: '水浸报警' })
  if (offline) faultTypes.push({ type: 'offline', name: '通讯故障' })
  if (lowBattery) faultTypes.push({ type: 'battery', name: '电池低电量' })
  if (lowSignal) faultTypes.push({ type: 'signal', name: '信号弱' })

  const hasAlert = faultTypes.length > 0

  const parts = []
  if (tilt) parts.push(`倾斜角度${tiltAngle}°超过阈值${angleThreshold}°`)
  if (waterInfiltration) parts.push('水浸报警')
  if (waterLevel) parts.push('水位超标报警')
  if (lowBattery) parts.push(`电量仅剩${battery}%`)
  if (lowSignal) parts.push(`信号强度${signal}%`)
  if (offline) parts.push('设备离线')

  let elapsedHours = 0
  const faultTime = md.uploadTime || md.createTime
  if (faultTime) {
    const diff = Date.now() - new Date(faultTime).getTime()
    elapsedHours = Math.max(0, Math.round(diff / (1000 * 60 * 60) * 10) / 10)
  }

  return {
    id: raw?.equipmentId || raw?.equipmentCode || '',
    code: raw?.equipmentCode || raw?.equipmentId || '',
    equipmentId: raw?.equipmentId || '',
    name: raw?.equipmentName || raw?.equipmentCode || '未命名设备',
    typeId,
    deviceTypeName: TYPE_MAP[typeId] || raw?.equipmentTypeName || '未知类型',
    location: raw?.equipmentLocation || '暂无位置信息',
    locationShort: (raw?.equipmentLocation || '暂无位置').slice(0, 20),
    district: raw?.district || '',
    battery, signal, temperature, tiltAngle, angleThreshold,
    status: { tilt, waterInfiltration, waterLevel, lowBattery, lowSignal, offline },
    faultTypes, hasAlert,
    faultDesc: parts.join('；') || '设备状态正常',
    abnormalTime: faultTime || '',
    elapsedHours,
    workOrderSubmitted: !!raw?.workOrderSubmitted
  }
}

// ==================== 数据加载 ====================
async function loadAll() {
  loading.value = true
  try {
    const today = new Date()
    const sixMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 5, 1)

    // 日期范围：默认近6个月，后端统一计算全部统计数据
    const startDate = dateRange.value?.[0] ? formatDate(new Date(dateRange.value[0])) : formatDate(sixMonthsAgo)
    const endDate = dateRange.value?.[1] ? formatDate(new Date(dateRange.value[1])) : formatDate(today)

    const { data } = await getManholeMaintenanceDashboard({ trendType: 2, startDate, endDate })

    // 1. KPI 指标
    const stat = data?.kpi || {}
    kpi.total = stat.totalTerminal || 0
    kpi.abnormal = stat.abnormalTerminal || 0
    kpi.abnormalRate = stat.abnormalRate ?? '0.0'
    kpi.submittedCount = stat.submittedCount || 0
    kpi.solvedRate = stat.solvedRate ?? 0
    kpi.totalCase = stat.totalCase || 0
    kpi.disposalRate = stat.disposalRate ?? 0
    kpi.delayRate = stat.delayRate ?? 0
    kpi.avgResponseTime = stat.avgDisposalTime ?? '—'

    // 2. 异常终端列表
    abnormalDevices.value = (data?.abnormalDevices || []).map(normalizeDevice)

    // 3. 渲染图表
    await nextTick()
    renderTrendChart(data?.abnormalTerminalTrend || [])
    renderProblemTypeChart(data?.problemTypeDistribution || [])
    renderDisposalTrendChart(data?.caseRateTrend || [])
    renderRegionRankChart(data?.regionRank || [])
  } catch (error) {
    const msg = error?.message || error?.msg || '未知错误'
    ElMessage.error('统计数据加载失败: ' + msg)
  } finally {
    loading.value = false
  }
}

// ==================== ECharts 渲染 ====================
function renderTrendChart(trendData) {
  if (!trendChartRef.value) return
  if (!trendChart) trendChart = echarts.init(trendChartRef.value)

  const labels = (trendData || []).map(d => d.trendLabel || '')
  const abnormalValues = (trendData || []).map(d => d.abnormalTerminal || 0)
  const normalValues = (trendData || []).map(d => {
    const abnormal = d.abnormalTerminal || 0
    const total = d.totalTerminal || abnormal
    return Math.max(0, total - abnormal)
  })

  trendChart.setOption({
    tooltip: { trigger: 'axis' },
    legend: { data: ['异常终端数', '正常终端数'] },
    grid: { left: '8%', right: '5%', bottom: '12%', top: '15%' },
    xAxis: { type: 'category', data: labels.length ? labels : ['暂无数据'] },
    yAxis: { type: 'value', name: '数量' },
    series: [
      { name: '异常终端数', type: 'line', data: abnormalValues, smooth: true, lineStyle: { color: '#f56c6c' }, areaStyle: { opacity: 0.1 }, itemStyle: { color: '#f56c6c' } },
      { name: '正常终端数', type: 'line', data: normalValues, smooth: true, lineStyle: { color: '#67c23a' }, areaStyle: { opacity: 0.1 }, itemStyle: { color: '#67c23a' } }
    ]
  })
}

function renderProblemTypeChart(distribution) {
  if (!problemTypeChartRef.value) return
  if (!problemTypeChart) problemTypeChart = echarts.init(problemTypeChartRef.value)

  const colors = ['#f56c6c', '#e6a23c', '#409eff', '#909399', '#67c23a', '#9966ff']
  const data = (distribution || []).map((item, i) => ({
    name: item.name || '其他',
    value: item.value || 0,
    itemStyle: { color: colors[i % colors.length] }
  }))

  problemTypeChart.setOption({
    tooltip: { trigger: 'item', formatter: '{b}: {c} ({d}%)' },
    legend: { orient: 'vertical', left: 'left', textStyle: { fontSize: 11 } },
    series: [{
      type: 'pie', radius: '55%', center: ['55%', '50%'],
      data: data.length ? data : [{ name: '暂无数据', value: 0 }],
      label: { show: true, formatter: '{b}: {d}%' }
    }]
  })
}

function renderDisposalTrendChart(trendData) {
  if (!disposalTrendChartRef.value) return
  if (!disposalTrendChart) disposalTrendChart = echarts.init(disposalTrendChartRef.value)

  const labels = (trendData || []).map(d => d.trendLabel || '')
  const disposalRates = (trendData || []).map(d => Number(d.caseDisposalRate) || 0)
  const delayRates = (trendData || []).map(d => Number(d.caseDelayRate) || 0)

  disposalTrendChart.setOption({
    tooltip: { trigger: 'axis' },
    legend: { data: ['处置率(%)', '延期率(%)'] },
    grid: { left: '8%', right: '5%', bottom: '12%', top: '15%' },
    xAxis: { type: 'category', data: labels.length ? labels : ['暂无数据'] },
    yAxis: { type: 'value', name: '百分比(%)', max: 100 },
    series: [
      { name: '处置率(%)', type: 'line', data: disposalRates, smooth: true, itemStyle: { color: '#67c23a' } },
      { name: '延期率(%)', type: 'line', data: delayRates, smooth: true, itemStyle: { color: '#e6a23c' } }
    ]
  })
}

function renderRegionRankChart(regionRank) {
  if (!regionRankChartRef.value) return
  if (!regionRankChart) regionRankChart = echarts.init(regionRankChartRef.value)

  const entries = (regionRank || []).slice(0, 8)

  regionRankChart.setOption({
    tooltip: {
      trigger: 'axis', axisPointer: { type: 'shadow' },
      formatter: (params) => {
        const p = params[0]
        const item = entries[p.dataIndex]
        return item ? `${p.name}: 异常 ${p.value} / 总数 ${item.total}` : p.name
      }
    },
    grid: { left: '15%', right: '8%', bottom: '8%', top: '8%' },
    xAxis: { type: 'value', name: '异常数量' },
    yAxis: { type: 'category', data: entries.map(e => e.name), axisLabel: { fontSize: 11 } },
    series: [{
      name: '异常数量', type: 'bar',
      data: entries.map(e => e.abnormal),
      itemStyle: { color: '#409eff', borderRadius: [0, 4, 4, 0] },
      label: { show: true, position: 'right', fontSize: 11 }
    }]
  })
}

// ==================== 计算属性 ====================
const filteredAbnormalDevices = computed(() => {
  let list = abnormalDevices.value
  if (searchKeyword.value) {
    const kw = searchKeyword.value.toLowerCase()
    list = list.filter(d =>
      d.code.toLowerCase().includes(kw) ||
      d.name.toLowerCase().includes(kw) ||
      d.location.toLowerCase().includes(kw)
    )
  }
  if (faultTypeFilter.value) {
    list = list.filter(d => d.faultTypes.some(ft => ft.type === faultTypeFilter.value))
  }
  return list
})

const paginatedAbnormalDevices = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  return filteredAbnormalDevices.value.slice(start, start + pageSize.value)
})

// ==================== 辅助函数 ====================
const getFaultTagType = (type) => {
  const map = { tilt: 'danger', waterLevel: 'danger', waterInfiltration: 'danger', offline: 'info', battery: 'warning', signal: 'warning' }
  return map[type] || 'info'
}

const viewDetail = (device) => {
  currentDevice.value = device
  detailDrawerVisible.value = true
}

// ==================== 生命周期 ====================
const handleResize = () => {
  trendChart && trendChart.resize()
  problemTypeChart && problemTypeChart.resize()
  disposalTrendChart && disposalTrendChart.resize()
  regionRankChart && regionRankChart.resize()
}

onMounted(async () => {
  await nextTick()
  window.addEventListener('resize', handleResize)
  loadAll()
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize)
  trendChart && trendChart.dispose()
  problemTypeChart && problemTypeChart.dispose()
  disposalTrendChart && disposalTrendChart.dispose()
  regionRankChart && regionRankChart.dispose()
})
</script>

<style scoped>
.terminal-operation-dashboard {
  background: #f0f2f6;
  min-height: 100vh;
  padding: 20px;
}
.page-header {
  background: white;
  border-radius: 16px;
  padding: 16px 24px;
  margin-bottom: 20px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
}
.header-left { display: flex; align-items: center; gap: 12px; }
.header-left .title { font-size: 20px; font-weight: 600; }
.header-right { display: flex; gap: 12px; flex-wrap: wrap; }

.kpi-cards {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 16px;
  margin-bottom: 20px;
}
.kpi-card {
  background: white;
  border-radius: 16px;
  padding: 16px;
  text-align: center;
  box-shadow: 0 1px 4px rgba(0,0,0,0.05);
}
.kpi-value { font-size: 32px; font-weight: bold; color: #1f2d3d; }
.kpi-label { font-size: 13px; color: #909399; margin-top: 6px; }
.kpi-trend { font-size: 11px; color: #67c23a; margin-top: 4px; }
.kpi-trend.trend-down { color: #f56c6c; }
.kpi-card.warning .kpi-value { color: #e6a23c; }
.kpi-card.success .kpi-value { color: #67c23a; }

.charts-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 20px;
}
.chart-card {
  background: white;
  border-radius: 16px;
  padding: 16px;
}
.chart-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  font-weight: 600;
}
.chart-container { height: 280px; }

.device-list-container {
  background: white;
  border-radius: 16px;
  padding: 16px;
}
.list-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 16px;
  font-weight: 600;
}
.list-filters { display: flex; gap: 12px; align-items: center; }
.pagination-wrapper {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
  padding-top: 12px;
  border-top: 1px solid #e9ecef;
}
.device-detail { padding: 8px; }
.sub-title { font-weight: 600; margin: 16px 0 12px 0; }
.over-time { color: #f56c6c; font-weight: 600; }

.monitor-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
.monitor-item {
  background: #f5f7fa;
  border-radius: 8px;
  padding: 10px;
  text-align: center;
  border: 1px solid #e9ecef;
}
.monitor-item.alert {
  background: #fef0f0;
  border-color: #f56c6c;
}
.m-label { font-size: 12px; color: #909399; }
.m-value { font-size: 16px; font-weight: 600; color: #303133; margin: 4px 0; }
.monitor-item.alert .m-value { color: #f56c6c; }
.m-extra { font-size: 11px; color: #aaa; }

@media (max-width: 1200px) {
  .kpi-cards { grid-template-columns: repeat(3, 1fr); }
  .charts-row { grid-template-columns: 1fr; }
}
</style>
