Files
delivery-tracker/frontend/src/App.tsx
T
chedius dee912edb8
Build and Push Docker Images / build-backend (push) Canceled after 0s
Build and Push Docker Images / build-frontend (push) Canceled after 0s
Improve delivery workflows and add versioned guided tours (#1)
Reviewed-on: #1
Co-authored-by: Egor Pozharov <chedius232@gmail.com>
2026-10-07 19:49:10 +00:00

65 lines
4.2 KiB
TypeScript

import { useState, useEffect, lazy, Suspense } from 'react';
import { Truck, Loader2, LogOut, CircleHelp } from 'lucide-react';
import { DeliveryForm } from './components/delivery/DeliveryForm';
import { LoginForm } from './components/auth/LoginForm';
import { ToastContainer } from './components/ui/Toast';
import { Button } from './components/ui/Button';
import { UpdatePrompt } from './components/ui/UpdatePrompt';
import { GuidedTour } from './components/ui/GuidedTour';
import { useDeliveryStore } from './stores/deliveryStore';
import { useAuthStore } from './stores/authStore';
import { useToastStore } from './stores/toastStore';
import { useWebSocket } from './hooks/useWebSocket';
import { getTodayFrontend } from './utils/date';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const DeliveryListPage = lazy(() => import('./pages/DeliveryListPage'));
const PageLoader = () => <div role="status" className="flex items-center justify-center gap-3 py-20 text-[#666870]"><Loader2 size={24} className="animate-spin" />Загрузка…</div>;
function App() {
const [view, setView] = useState<'dashboard' | 'delivery-list'>('dashboard');
const [selectedDate, setSelectedDate] = useState(getTodayFrontend);
const [formOpen, setFormOpen] = useState(false);
const [tourReplay, setTourReplay] = useState(0);
const { isAuthenticated, isAuthChecking, user, restoreAuth, logout } = useAuthStore();
const { addDelivery, isLoading, isLoadingCounts, error, countsError, currentDate } = useDeliveryStore();
useWebSocket();
useEffect(() => { restoreAuth(); }, [restoreAuth]);
const selectDate = (date: string) => { setSelectedDate(date); setView('delivery-list'); };
const submitDelivery = async (data: Parameters<typeof addDelivery>[0]) => {
await addDelivery(data);
useToastStore.getState().addToast('Доставка создана', 'success');
selectDate(data.date);
};
if (isAuthChecking) return <PageLoader />;
if (!isAuthenticated) return <><UpdatePrompt /><LoginForm /><ToastContainer /></>;
return (
<div className="min-h-screen bg-[#fbf8fb]">
<a href="#main-content" className="skip-link">Перейти к содержимому</a>
<UpdatePrompt />
<header className="sticky top-0 z-40 bg-[#1B263B] text-white">
<div className="mx-auto flex min-h-16 max-w-7xl items-center justify-between gap-2 px-4 sm:px-6 lg:px-8">
<button type="button" onClick={() => setView('dashboard')} className="flex min-h-11 items-center gap-2 text-left" aria-label="Delivery Tracker — открыть план доставок">
<Truck size={25} className="shrink-0" /><span className="text-sm font-semibold sm:text-lg">Delivery Tracker</span>
</button>
<div className="flex items-center gap-1 sm:gap-3">
<Button variant="ghost" size="sm" data-tour="tour-help" onClick={() => setTourReplay(value => value + 1)} className="text-white hover:bg-white/10" aria-label="Помощь: обзор текущего экрана"><CircleHelp size={19} /><span className="ml-2 hidden sm:inline">Помощь</span></Button>
<Button variant="ghost" size="sm" onClick={logout} className="text-white hover:bg-white/10" aria-label="Выйти из системы"><LogOut size={18} /><span className="ml-2 hidden sm:inline">Выйти</span></Button>
</div>
</div>
</header>
<main id="main-content" tabIndex={-1} className="mx-auto max-w-7xl px-4 py-5 sm:px-6 sm:py-7 lg:px-8">
<Suspense fallback={<PageLoader />}>
{view === 'dashboard' ? <Dashboard onDateSelect={selectDate} onAddDelivery={() => setFormOpen(true)} />
: <DeliveryListPage selectedDate={selectedDate} onDateSelect={selectDate} onBack={() => setView('dashboard')} />}
</Suspense>
</main>
<DeliveryForm isOpen={formOpen} onClose={() => setFormOpen(false)} onSubmit={submitDelivery} defaultDate={getTodayFrontend()} />
<GuidedTour key={`${user?.id || user?.username}:${view}`} scope={view} userKey={user?.id || user?.username || 'user'} replay={tourReplay}
ready={!formOpen && (view === 'dashboard' ? !isLoadingCounts && !countsError : !isLoading && !error && currentDate === selectedDate)} />
<ToastContainer />
</div>
);
}
export default App;