diff --git a/src/App.jsx b/src/App.jsx
index 3923a47..9ed6ae9 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -21,6 +21,7 @@ const UserManagementPage = React.lazy(() => import('./admin/users/UserManagement
const SettingsPage = React.lazy(() => import('./admin/settings/SettingsPage'));
const ClientsPage = React.lazy(() => import('./admin/clients/ClientsPage'));
const CalendarPage = React.lazy(() => import('./admin/calendar/CalendarPage'));
+const ArchivePage = React.lazy(() => import('./admin/archive/ArchivePage'));
const OrderTrackingPage = React.lazy(() => import('./portal/OrderTrackingPage'));
function PublicSite() {
@@ -60,6 +61,7 @@ function App() {
} />
} />
} />
+ } />
diff --git a/src/admin/archive/ArchivePage.jsx b/src/admin/archive/ArchivePage.jsx
new file mode 100644
index 0000000..b25ba0b
--- /dev/null
+++ b/src/admin/archive/ArchivePage.jsx
@@ -0,0 +1,128 @@
+import { useState } from 'react';
+import { Archive, RotateCcw } from 'lucide-react';
+import { useArchive } from './useArchive';
+import OrderModal from '../orders/OrderModal';
+
+const thStyle = {
+ padding: '10px 14px', textAlign: 'left',
+ fontSize: '0.72rem', fontWeight: 700, color: 'var(--text-muted)',
+ textTransform: 'uppercase', letterSpacing: '0.4px',
+ borderBottom: '1.5px solid var(--border-light)',
+ fontFamily: 'var(--font-heading)',
+};
+
+const tdStyle = {
+ padding: '12px 14px', fontSize: '0.88rem', color: 'var(--text-main)',
+ borderBottom: '1px solid var(--border-light)', fontFamily: 'var(--font-body)',
+};
+
+const formatDate = (s) => {
+ if (!s) return '—';
+ const d = new Date(s);
+ return isNaN(d) ? '—' : d.toLocaleDateString('ru-RU');
+};
+
+const ArchivePage = () => {
+ const { orders, loading, restoreOrder } = useArchive();
+ const [selectedOrder, setSelectedOrder] = useState(null);
+ const [restoringId, setRestoringId] = useState(null);
+
+ const handleRestore = async (e, order) => {
+ e.stopPropagation();
+ setRestoringId(order.id);
+ await restoreOrder(order.id);
+ setRestoringId(null);
+ };
+
+ return (
+
+
+
+ Архив
+
+
+ Закрытые заявки старше 30 дней. Нажмите на строку для просмотра, или восстановите в статус «новая».
+
+
+
+ {loading ? (
+
+ Загрузка...
+
+ ) : orders.length === 0 ? (
+
+ Архив пуст — нет закрытых заявок старше 30 дней
+
+ ) : (
+
+
+
+
+ | Клиент |
+ Телефон |
+ Сумма |
+ Закрыта |
+ Менеджер |
+ |
+
+
+
+ {orders.map((o) => (
+ setSelectedOrder(o)}
+ style={{ cursor: 'pointer', transition: 'background 0.1s' }}
+ onMouseEnter={(e) => e.currentTarget.style.background = '#F5F7FA'}
+ onMouseLeave={(e) => e.currentTarget.style.background = ''}
+ >
+ | {o.name} |
+ {o.phone || '—'} |
+
+ {o.final_cost ? `${Number(o.final_cost).toLocaleString('ru-RU')} ₽` : '—'}
+ |
+ {formatDate(o.closed_at || o.created_at)} |
+ {o.assigned_to || '—'} |
+
+
+ |
+
+ ))}
+
+
+
+ )}
+
+
+ {selectedOrder && (
+
setSelectedOrder(null)}
+ onSaved={() => setSelectedOrder(null)}
+ />
+ )}
+
+ );
+};
+
+export default ArchivePage;
diff --git a/src/admin/archive/useArchive.js b/src/admin/archive/useArchive.js
new file mode 100644
index 0000000..543d4d5
--- /dev/null
+++ b/src/admin/archive/useArchive.js
@@ -0,0 +1,37 @@
+import { useState, useEffect, useCallback } from 'react';
+import { supabase } from '../../lib/supabase';
+
+export function useArchive() {
+ const [orders, setOrders] = useState([]);
+ const [loading, setLoading] = useState(true);
+
+ const fetch = useCallback(async () => {
+ let isMounted = true;
+ setLoading(true);
+ const cutoff = new Date();
+ cutoff.setDate(cutoff.getDate() - 30);
+
+ const { data } = await supabase
+ .from('orders')
+ .select('id, name, phone, status, final_cost, created_at, closed_at, assigned_to, film_thickness, area_sqm, address')
+ .eq('status', 'закрыт')
+ .lt('created_at', cutoff.toISOString())
+ .order('closed_at', { ascending: false, nullsFirst: false });
+
+ if (isMounted) { setOrders(data ?? []); setLoading(false); }
+ return () => { isMounted = false; };
+ }, []);
+
+ useEffect(() => { fetch(); }, [fetch]);
+
+ const restoreOrder = async (orderId) => {
+ const { error } = await supabase
+ .from('orders')
+ .update({ status: 'новая', closed_at: null })
+ .eq('id', orderId);
+ if (!error) await fetch();
+ return { error };
+ };
+
+ return { orders, loading, restoreOrder, refetch: fetch };
+}
diff --git a/src/admin/layout/Sidebar.jsx b/src/admin/layout/Sidebar.jsx
index 3566ba8..b38d478 100644
--- a/src/admin/layout/Sidebar.jsx
+++ b/src/admin/layout/Sidebar.jsx
@@ -1,10 +1,11 @@
import { NavLink, useNavigate } from 'react-router-dom';
-import { LayoutDashboard, BarChart2, CheckSquare, Shield, LogOut, Users, Settings, BookUser, X, CalendarDays } from 'lucide-react';
+import { LayoutDashboard, BarChart2, CheckSquare, Shield, LogOut, Users, Settings, BookUser, X, CalendarDays, Archive } from 'lucide-react';
import { useAuth } from '../auth/useAuth';
const NAV_ITEMS = [
{ to: '/admin/kanban', icon: LayoutDashboard, label: 'Канбан' },
{ to: '/admin/analytics', icon: BarChart2, label: 'Аналитика' },
+ { to: '/admin/archive', icon: Archive, label: 'Архив' },
{ to: '/admin/completed', icon: CheckSquare, label: 'Завершённые' },
{ to: '/admin/calendar', icon: CalendarDays, label: 'Календарь' },
{ to: '/admin/users', icon: Users, label: 'Пользователи' },