import { useQuery } from '@tanstack/react-query' import api from '@/lib/api' import { CollapsibleSection } from '@/components/CollapsibleSection' import { MetricCard } from '@/components/MetricCard' import { DataTable } from '@/components/DataTable' import { LoadingSpinner } from '@/components/LoadingSpinner' import { Pagination } from '@/components/Pagination' import { formatNumber, formatPercent } from '@/lib/utils' import { useState } from 'react' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts' const PAGE_SIZE = 15 export function BomTab() { const [complexPage, setComplexPage] = useState(1) const [missingPage, setMissingPage] = useState(1) const [selectedSku, setSelectedSku] = useState('') const { data: bomOv, isLoading: l1 } = useQuery({ queryKey: ['ca/bom-overview'], queryFn: () => api.get('/cost-analysis/bom-overview') }) const { data: complexity, isLoading: l2 } = useQuery({ queryKey: ['ca/bom-complexity', complexPage], queryFn: () => api.get(`/cost-analysis/bom-complexity?page=${complexPage}&page_size=${PAGE_SIZE}`) }) const { data: highLoss, isLoading: l3 } = useQuery({ queryKey: ['ca/bom-high-loss'], queryFn: () => api.get('/cost-analysis/bom-high-loss?threshold=20') }) const { data: missing, isLoading: l4 } = useQuery({ queryKey: ['ca/bom-missing', missingPage], queryFn: () => api.get(`/cost-analysis/bom-missing?page=${missingPage}&page_size=${PAGE_SIZE}`) }) const { data: composition, isLoading: l5 } = useQuery({ queryKey: ['ca/bom-composition', selectedSku], queryFn: () => api.get(`/cost-analysis/bom-composition?sku_code=${selectedSku}`), enabled: !!selectedSku, }) if (l1 || l2 || l3 || l4) return const ov = (bomOv as any)?.data || {} const compData = (complexity as any)?.data || [] const compMeta = (complexity as any)?.meta || { total: 0 } const lossData = (highLoss as any)?.data || [] const missData = (missing as any)?.data || [] const missMeta = (missing as any)?.meta || { total: 0 } const compDetail = (composition as any)?.data || [] const complexityColor = (level: string) => { if (level === '重点评审') return 'bg-red-100 text-red-700' if (level === '较复杂') return 'bg-orange-100 text-orange-700' if (level === '正常') return 'bg-blue-100 text-blue-700' return 'bg-green-100 text-green-700' } const alertColor = (level: string) => { if (level === '极端') return 'bg-red-100 text-red-700' if (level === '严重') return 'bg-orange-100 text-orange-700' return 'bg-yellow-100 text-yellow-700' } const complexityDist = [ { band: '≤5', cnt: compData.filter((d: any) => d.material_count <= 5).length }, { band: '6-10', cnt: compData.filter((d: any) => d.material_count > 5 && d.material_count <= 10).length }, { band: '11-15', cnt: compData.filter((d: any) => d.material_count > 10 && d.material_count <= 15).length }, { band: '>15', cnt: compData.filter((d: any) => d.material_count > 15).length }, ] return (
( setSelectedSku(r.sku_code)}>{r.dish_name} )}, { key: 'category_l1', label: '品类' }, { key: 'material_count', label: '原料数', align: 'right', render: (r) => formatNumber(r.material_count) }, { key: 'semi_finished_count', label: '半成品数', align: 'right', render: (r) => formatNumber(r.semi_finished_count) }, { key: 'unique_material_count', label: '独有原料', align: 'right', render: (r) => formatNumber(r.unique_material_count) }, { key: 'high_loss_count', label: '高损耗原料', align: 'right', render: (r) => r.high_loss_count > 0 ? {r.high_loss_count} : '0' }, { key: 'complexity_level', label: '复杂度', render: (r) => ( {r.complexity_level} )}, ]} data={compData} />
{selectedSku && ( {l5 ? : ( formatNumber(r.gross_qty) }, { key: 'net_qty', label: '标准净用量', align: 'right', render: (r) => formatNumber(r.net_qty) }, { key: 'unit', label: '单位', align: 'center' }, { key: 'waste_rate', label: '损耗率', align: 'right', render: (r) => formatPercent(r.waste_rate) }, { key: 'cost_share', label: '成本占比', align: 'right', render: (r) => formatPercent(r.cost_share) }, ]} data={compDetail} /> )} )} {formatPercent(r.waste_rate)} }, { key: 'yield_rate', label: '出成率', align: 'right', render: (r) => formatPercent(r.yield_rate) }, { key: 'unit', label: '单位', align: 'center' }, { key: 'alert_level', label: '预警级别', render: (r) => ( {r.alert_level} )}, ]} data={lossData} />
r.sales_amount > 0 ? `¥${formatNumber(r.sales_amount)}` : '-' }, { key: 'sales_quantity', label: '销量', align: 'right', render: (r) => r.sales_quantity > 0 ? formatNumber(r.sales_quantity) : '-' }, ]} data={missData} />
) }