feat: CSV export from analytics page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { TrendingUp } from 'lucide-react';
|
import { TrendingUp, Download } from 'lucide-react';
|
||||||
import { useAnalyticsPage } from './useAnalyticsPage';
|
import { useAnalyticsPage } from './useAnalyticsPage';
|
||||||
|
|
||||||
const MONTHS_RU = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'];
|
const MONTHS_RU = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек'];
|
||||||
@@ -103,6 +103,28 @@ const AnalyticsPage = () => {
|
|||||||
return Object.values(map).sort((a, b) => b.revenue - a.revenue);
|
return Object.values(map).sort((a, b) => b.revenue - a.revenue);
|
||||||
}, [orders]);
|
}, [orders]);
|
||||||
|
|
||||||
|
const handleExportCSV = () => {
|
||||||
|
const BOM = '';
|
||||||
|
const headers = ['ID', 'Статус', 'Сумма', 'Дата создания', 'Менеджер', 'Плёнка'];
|
||||||
|
const rows = orders.map((o) => [
|
||||||
|
o.id,
|
||||||
|
o.status ?? '',
|
||||||
|
o.final_cost ?? '',
|
||||||
|
o.created_at ? new Date(o.created_at).toLocaleDateString('ru-RU') : '',
|
||||||
|
o.assigned_to ?? '',
|
||||||
|
o.film_thickness ?? '',
|
||||||
|
].map((v) => `"${String(v).replace(/"/g, '""')}"`).join(','));
|
||||||
|
|
||||||
|
const csv = BOM + [headers.join(','), ...rows].join('\n');
|
||||||
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = `orders-${new Date().toISOString().slice(0, 10)}.csv`;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
};
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 30, textAlign: 'center', color: 'var(--text-muted)', paddingTop: 80 }}>
|
<div style={{ padding: 30, textAlign: 'center', color: 'var(--text-muted)', paddingTop: 80 }}>
|
||||||
@@ -119,16 +141,28 @@ const AnalyticsPage = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={{ padding: 30, maxWidth: 900 }}>
|
<div style={{ padding: 30, maxWidth: 900 }}>
|
||||||
{/* Page title */}
|
{/* Page title */}
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 28 }}>
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '24px' }}>
|
||||||
<TrendingUp size={22} color="var(--accent-blue)" />
|
<h1 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.5rem', fontWeight: 800, display: 'flex', alignItems: 'center', gap: '10px' }}>
|
||||||
<h1 style={{
|
<TrendingUp size={22} color="var(--accent-blue)" />
|
||||||
fontFamily: 'var(--font-heading)',
|
|
||||||
fontWeight: 800,
|
|
||||||
fontSize: '1.45rem',
|
|
||||||
color: 'var(--text-main)',
|
|
||||||
}}>
|
|
||||||
Аналитика
|
Аналитика
|
||||||
</h1>
|
</h1>
|
||||||
|
<button
|
||||||
|
onClick={handleExportCSV}
|
||||||
|
disabled={loading || orders.length === 0}
|
||||||
|
style={{
|
||||||
|
display: 'inline-flex', alignItems: 'center', gap: '6px',
|
||||||
|
padding: '8px 16px', borderRadius: '8px', cursor: 'pointer',
|
||||||
|
background: 'transparent', border: '1.5px solid #E9ECEF',
|
||||||
|
fontSize: '0.88rem', fontWeight: 600, color: '#1E1E2D',
|
||||||
|
fontFamily: 'var(--font-heading)',
|
||||||
|
opacity: (loading || orders.length === 0) ? 0.5 : 1,
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => e.currentTarget.style.borderColor = '#4361EE'}
|
||||||
|
onMouseLeave={(e) => e.currentTarget.style.borderColor = '#E9ECEF'}
|
||||||
|
>
|
||||||
|
<Download size={15} />
|
||||||
|
Экспорт CSV
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Section A — Summary cards */}
|
{/* Section A — Summary cards */}
|
||||||
|
|||||||
Reference in New Issue
Block a user