Files
aiform_prod/frontend/src/App.tsx
Fedor 66a0065df8 Consultations, CRM dashboard, Back button in support and consultations
- Consultations: list from DraftsContext, ticket-detail webhook, response card
- Back button in bar on consultations and in support chat (miniapp:goBack)
- BottomBar: back enabled on /support; Support: goBack subscription
- n8n: CRM normalize (n8n_CODE_CRM_NORMALIZE), flatten data (n8n_CODE_FLATTEN_DATA)
- Dashboard: filter by category for CRM items, draft card width
- Backend: consultations.py, ticket-detail, n8n_ticket_form_podrobnee_webhook
- CHANGELOG_MINIAPP.md: section 2026-02-25
2026-03-01 10:49:38 +03:00

112 lines
4.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState, useEffect, useCallback, useRef } from 'react';
import ClaimForm from './pages/ClaimForm';
import HelloAuth from './pages/HelloAuth';
import Profile from './pages/Profile';
import Support from './pages/Support';
import Consultations from './pages/Consultations';
import BottomBar from './components/BottomBar';
import { DraftsProvider } from './context/DraftsContext';
import './App.css';
import { miniappLog, miniappSendLogs } from './utils/miniappLogger';
function App() {
const [pathname, setPathname] = useState<string>(() => {
const p = window.location.pathname || '';
if (p !== '/hello' && !p.startsWith('/hello')) return '/hello';
return p;
});
const [avatarUrl, setAvatarUrl] = useState<string>(() => localStorage.getItem('user_avatar_url') || '');
const [profileNeedsAttention, setProfileNeedsAttention] = useState<boolean>(false);
const lastRouteTsRef = useRef<number>(Date.now());
const lastPathRef = useRef<string>(pathname);
useEffect(() => {
const path = window.location.pathname || '/';
if (path !== '/hello' && !path.startsWith('/hello')) {
window.history.replaceState({}, '', '/hello' + (window.location.search || '') + (window.location.hash || ''));
}
}, []);
useEffect(() => {
const onPopState = () => setPathname(window.location.pathname || '');
window.addEventListener('popstate', onPopState);
return () => window.removeEventListener('popstate', onPopState);
}, []);
// Логируем смену маршрута + ловим быстрый возврат на /hello (симптом бага)
useEffect(() => {
const now = Date.now();
const prev = lastPathRef.current;
lastPathRef.current = pathname;
lastRouteTsRef.current = now;
miniappLog('route', { prev, next: pathname });
if (pathname.startsWith('/hello') && !prev.startsWith('/hello')) {
// Вернулись на /hello: отправим дамп, чтобы поймать “ложится”
void miniappSendLogs('returned_to_hello');
}
}, [pathname]);
// Ловим клики в первые 2с после смены маршрута (ghost click / попадание в бар)
useEffect(() => {
const onClickCapture = (e: MouseEvent) => {
const dt = Date.now() - lastRouteTsRef.current;
if (dt > 2000) return;
const t = e.target as HTMLElement | null;
const inBar = !!t?.closest?.('.app-bottom-bar');
miniappLog('click_capture', {
dtFromRouteMs: dt,
inBottomBar: inBar,
tag: t?.tagName,
id: t?.id,
class: t?.className,
x: (e as MouseEvent).clientX,
y: (e as MouseEvent).clientY,
});
};
window.addEventListener('click', onClickCapture, true);
return () => window.removeEventListener('click', onClickCapture, true);
}, []);
useEffect(() => {
setAvatarUrl(localStorage.getItem('user_avatar_url') || '');
}, [pathname]);
const isNewClaimPage = pathname === '/new';
const navigateTo = useCallback((path: string) => {
window.history.pushState({}, '', path);
setPathname(path);
}, []);
return (
<DraftsProvider>
<div className="App">
{pathname === '/profile' ? (
<Profile onNavigate={navigateTo} />
) : pathname === '/support' ? (
<Support onNavigate={navigateTo} />
) : pathname === '/consultations' ? (
<Consultations onNavigate={navigateTo} />
) : pathname.startsWith('/hello') ? (
<HelloAuth
onAvatarChange={setAvatarUrl}
onNavigate={navigateTo}
onProfileNeedsAttentionChange={setProfileNeedsAttention}
/>
) : (
<ClaimForm forceNewClaim={isNewClaimPage} onNavigate={navigateTo} />
)}
<BottomBar
currentPath={pathname}
avatarUrl={avatarUrl || undefined}
profileNeedsAttention={profileNeedsAttention}
onNavigate={navigateTo}
/>
</div>
</DraftsProvider>
);
}
export default App;