Hooks & Contexts
Na tej stronie
Organizacja hooków
Dział zatytułowany „Organizacja hooków”Vista zawiera 58 custom hooks zorganizowanych domenowo:
src/hooks/├── audio/ # 8 hooków audio├── visits/ # 6 hooków wizyt├── patients/ # 4 hooki pacjentów├── auth/ # 3 hooki auth├── calendar/ # 4 hooki kalendarza├── ai/ # 5 hooków AI├── settings/ # 3 hooki ustawień├── ui/ # 6 hooków UI└── utils/ # 19 utility hooksAudio Hooks
Dział zatytułowany „Audio Hooks”useRecording
Dział zatytułowany „useRecording”Główny hook do nagrywania audio wizyt.
interface UseRecordingReturn { // State isRecording: boolean; isPaused: boolean; duration: number; // seconds audioLevel: number; // 0-100 (for visualisation)
// Actions startRecording: () => Promise<void>; stopRecording: () => Promise<Blob>; pauseRecording: () => void; resumeRecording: () => void;
// Errors error: Error | null; permissionDenied: boolean;}
// Użycieconst { isRecording, duration, startRecording, stopRecording,} = useRecording({ onRecordingComplete: (audioBlob) => handleAudio(audioBlob), maxDuration: 3600, // 1 hour max});useVoiceActivityDetection
Dział zatytułowany „useVoiceActivityDetection”Automatyczne wykrywanie mowy dla smart recording.
interface VADConfig { threshold: number; // -50 to 0 dB (default: -45) minSilenceDuration: number; // ms before auto-stop (default: 1500) bufferSize: number; // Audio buffer size (default: 4096) smoothingFrames: number; // Noise reduction (default: 3)}
const { isVoiceActive, currentLevel } = useVoiceActivityDetection({ threshold: -45, minSilenceDuration: 1500, onSilenceDetected: () => { // Auto-pause or notify user },});useAudioPlayer
Dział zatytułowany „useAudioPlayer”Odtwarzanie nagrań z wizyt.
const { isPlaying, currentTime, duration, play, pause, seek, setPlaybackRate,} = useAudioPlayer(audioUrl);Visit Hooks
Dział zatytułowany „Visit Hooks”useVisit
Dział zatytułowany „useVisit”Główny hook do zarządzania wizytą.
interface UseVisitReturn { visit: Visit | null; isLoading: boolean; error: Error | null;
// CRUD updateVisit: (updates: Partial<Visit>) => Promise<void>; finalizeVisit: () => Promise<void>; deleteVisit: () => Promise<void>;
// SOAP updateSOAP: (field: SOAPField, value: string) => Promise<void>;
// Optimistic locking version: number; hasConflict: boolean;}
const { visit, updateSOAP, finalizeVisit,} = useVisit(visitId);useSOAPEditor
Dział zatytułowany „useSOAPEditor”Edycja notatek SOAP z auto-save.
const { subjective, objective, assessment, plan, setSubjective, setObjective, setAssessment, setPlan, isDirty, isSaving, save,} = useSOAPEditor(visitId, { autoSaveDelay: 2000, // 2 seconds debounce onAutoSave: (data) => console.log('Auto-saved:', data),});useVisitTranscription
Dział zatytułowany „useVisitTranscription”Transkrypcja audio wizyty.
const { transcript, isTranscribing, progress, // 0-100 speakerSegments, // Diarization results startTranscription, retryTranscription,} = useVisitTranscription(visitId, recordingId);Patient Hooks
Dział zatytułowany „Patient Hooks”usePatient
Dział zatytułowany „usePatient”Zarządzanie pacjentem.
const { patient, isLoading, updatePatient, deletePatient, visitHistory, upcomingAppointments,} = usePatient(patientId);usePatientSearch
Dział zatytułowany „usePatientSearch”Wyszukiwanie pacjentów z debounce.
const { results, isSearching, search, clearResults,} = usePatientSearch({ debounceMs: 300, minChars: 2,});
// W komponencie<input onChange={(e) => search(e.target.value)} placeholder="Szukaj pacjenta..."/>Auth Hooks
Dział zatytułowany „Auth Hooks”useAuth
Dział zatytułowany „useAuth”Główny hook autoryzacji.
const { user, isAuthenticated, isLoading,
// Actions login, logout, loginWithBiometric,
// Session sessionExpiresAt, refreshSession,} = useAuth();useBiometric
Dział zatytułowany „useBiometric”Obsługa Touch ID / Face ID.
const { isAvailable, // czy biometria dostępna isEnabled, // czy włączona dla usera authenticate, // uruchom auth enable, // włącz biometrię disable, // wyłącz biometrię} = useBiometric();
// Użycieconst handleBiometricLogin = async () => { const result = await authenticate('Zaloguj się do Vista'); if (result.success) { // User authenticated }};AI Hooks
Dział zatytułowany „AI Hooks”useAIChat
Dział zatytułowany „useAIChat”Czat z AI assistentem.
const { messages, isTyping, sendMessage, clearHistory, currentSessionId,} = useAIChat({ visitContext: visitId, onMessageReceived: (msg) => playNotificationSound(),});
// Wysłanie wiadomościawait sendMessage('Co sugerujesz dla tego pacjenta?');useAISuggestions
Dział zatytułowany „useAISuggestions”Sugestie AI dla wizyty.
const { suggestions, isGenerating, acceptSuggestion, dismissSuggestion, regenerate,} = useAISuggestions(visitId, { autoGenerate: true, // Generuj automatycznie po transkrypcji});UI Hooks
Dział zatytułowany „UI Hooks”useDebounce
Dział zatytułowany „useDebounce”const debouncedValue = useDebounce(searchQuery, 300);
useEffect(() => { if (debouncedValue) { performSearch(debouncedValue); }}, [debouncedValue]);useLocalStorage
Dział zatytułowany „useLocalStorage”const [theme, setTheme] = useLocalStorage('vista-theme', 'light');
// Automatycznie sync z localStoragesetTheme('dark');useMediaQuery
Dział zatytułowany „useMediaQuery”const isMobile = useMediaQuery('(max-width: 768px)');const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
return isMobile ? <MobileLayout /> : <DesktopLayout />;useClickOutside
Dział zatytułowany „useClickOutside”const dropdownRef = useRef<HTMLDivElement>(null);
useClickOutside(dropdownRef, () => { setIsOpen(false);});
return ( <div ref={dropdownRef}> {isOpen && <DropdownContent />} </div>);useKeyboardShortcut
Dział zatytułowany „useKeyboardShortcut”// Ctrl+N → nowa wizytauseKeyboardShortcut(['ctrl', 'n'], () => { navigateTo('new-visit');});
// Escape → zamknij modaluseKeyboardShortcut(['escape'], () => { closeModal();}, { enabled: isModalOpen });Context Providers
Dział zatytułowany „Context Providers”AuthContext
Dział zatytułowany „AuthContext”interface AuthContextValue { user: User | null; isAuthenticated: boolean; isLoading: boolean;
login: (credentials: LoginCredentials) => Promise<void>; logout: () => Promise<void>; loginWithBiometric: () => Promise<void>;
updateUser: (updates: Partial<User>) => Promise<void>; refreshSession: () => Promise<void>;}
// Provider w App.tsx<AuthProvider onSessionExpired={() => navigateTo('login')} sessionTimeout={30 * 60 * 1000} // 30 minutes> <App /></AuthProvider>
// Użycie w komponencieconst { user, logout } = useAuth();ThemeContext
Dział zatytułowany „ThemeContext”interface ThemeContextValue { theme: 'light' | 'dark' | 'system'; resolvedTheme: 'light' | 'dark'; setTheme: (theme: 'light' | 'dark' | 'system') => void;
// Galaxy theme galaxyEnabled: boolean; setGalaxyEnabled: (enabled: boolean) => void; galaxyConfig: GalaxyConfig; updateGalaxyConfig: (config: Partial<GalaxyConfig>) => void;}
const { theme, setTheme, galaxyEnabled } = useTheme();SidebarContext
Dział zatytułowany „SidebarContext”interface SidebarContextValue { // Navigation currentView: MenuItem; setCurrentView: (view: MenuItem) => void;
// Sidebar state isCollapsed: boolean; toggleSidebar: () => void; isMobileOpen: boolean; setMobileOpen: (open: boolean) => void;
// Deep linking navigationParams: Record<string, string>; navigateWithParams: (view: MenuItem, params: Record<string, string>) => void;}
const { currentView, setCurrentView, navigateWithParams } = useSidebar();
// Navigate to patient detailsnavigateWithParams('patients', { patientId: '123' });NotificationContext
Dział zatytułowany „NotificationContext”interface NotificationContextValue { // Toast notifications showSuccess: (message: string) => void; showError: (message: string) => void; showWarning: (message: string) => void; showInfo: (message: string) => void;
// Custom notifications notify: (notification: Notification) => void; dismiss: (id: string) => void; dismissAll: () => void;}
const { showSuccess, showError } = useNotification();
// Po zapisaniushowSuccess('Wizyta została zapisana');
// Przy błędzieshowError('Nie udało się zapisać wizyty');Custom Hook Patterns
Dział zatytułowany „Custom Hook Patterns”Data Fetching Pattern
Dział zatytułowany „Data Fetching Pattern”function useDataFetch<T>( fetcher: () => Promise<T>, deps: DependencyList = []) { const [data, setData] = useState<T | null>(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<Error | null>(null);
useEffect(() => { let isMounted = true;
const fetchData = async () => { setIsLoading(true); setError(null);
try { const result = await fetcher(); if (isMounted) { setData(result); } } catch (e) { if (isMounted) { setError(e as Error); } } finally { if (isMounted) { setIsLoading(false); } } };
fetchData();
return () => { isMounted = false; }; }, deps);
return { data, isLoading, error };}Optimistic Update Pattern
Dział zatytułowany „Optimistic Update Pattern”function useOptimisticUpdate<T>( initialData: T, updateFn: (data: T) => Promise<T>) { const [data, setData] = useState(initialData); const [isUpdating, setIsUpdating] = useState(false);
const update = async (newData: T) => { const previousData = data;
// Optimistic update setData(newData); setIsUpdating(true);
try { const result = await updateFn(newData); setData(result); } catch (error) { // Rollback on error setData(previousData); throw error; } finally { setIsUpdating(false); } };
return { data, update, isUpdating };}Testing Hooks
Dział zatytułowany „Testing Hooks”import { renderHook, act, waitFor } from '@testing-library/react';import { usePatientSearch } from '../usePatientSearch';
describe('usePatientSearch', () => { it('debounces search queries', async () => { const { result } = renderHook(() => usePatientSearch({ debounceMs: 100 }) );
act(() => { result.current.search('Bu'); result.current.search('Bur'); result.current.search('Bure'); result.current.search('Burek'); });
// Only last search should execute await waitFor(() => { expect(mockSearchFn).toHaveBeenCalledTimes(1); expect(mockSearchFn).toHaveBeenCalledWith('Burek'); }); });});