Przejdź do głównej zawartości

Frontend Overview

Ten dokument zbiera kluczowe filary frontendu Vista i sposób organizacji kodu.

Na tej stronie
graph TB
subgraph "Frontend Stack"
REACT["React 18.3.1<br/>UI Framework"]
TS["TypeScript 5.6.3<br/>Type Safety"]
TAILWIND["Tailwind CSS 4.1<br/>Styling"]
FRAMER["Framer Motion 12.23<br/>Animations"]
I18N["i18next 25.4<br/>Internationalization"]
end
subgraph "Build & Dev Tools"
VITE["Vite 6.3.5<br/>Build Tool"]
VITEST["Vitest 3.2.4<br/>Testing"]
STORYBOOK["Storybook 9.1<br/>Component Dev"]
end
REACT --> VITE
TS --> VITEST
KategoriaLiczbaLokalizacja
React Components~330src/components/
Custom Hooks170src/hooks/
Services50src/services/
Context Providers4src/contexts/
Languages2Polski (primary), English

src/
├── components/ # ~330 komponentów UI
│ ├── auth/ # Logowanie, biometria
│ ├── visits/ # Workflow wizyt (SOAP)
│ ├── patients/ # Zarządzanie pacjentami
│ ├── calendar/ # Kalendarz i wizyty
│ ├── settings/ # Konfiguracja
│ ├── ai/ # Komponenty AI
│ ├── audio/ # Przetwarzanie audio
│ ├── layout/ # Sidebar, navigation
│ ├── ui/ # Reusable UI (70+)
│ └── ...
├── hooks/ # 58 custom hooks
│ ├── audio/ # useRecording, useVAD
│ ├── visits/ # useVisit, useSOAP
│ └── ...
├── services/ # 50 serwisów
│ ├── ai/ # UnifiedAIClient
│ ├── patients/ # patientService
│ └── ...
├── contexts/ # 4 główne konteksty
│ ├── AuthContext.tsx
│ ├── ThemeContext.tsx
│ ├── SidebarContext.tsx
│ └── NotificationContext.tsx
├── types/ # TypeScript definitions
├── utils/ # Utility functions
├── i18n/ # Translations (PL/EN)
└── schemas/ # Zod validation schemas

Vista używa React Context API zamiast Redux/Zustand dla prostszego mental model.

// Hierarchia providerów w App.tsx
<AuthProvider>
<ThemeProvider>
<SidebarProvider>
<NotificationProvider>
<App />
</NotificationProvider>
</SidebarProvider>
</ThemeProvider>
</AuthProvider>
ContextOdpowiedzialnośćPlik
AuthContextUser authentication, sessions, biometric authsrc/contexts/AuthContext.tsx
ThemeContextLight/dark mode, Galaxy themesrc/contexts/ThemeContext.tsx
SidebarContextNavigation state, mobile sidebarsrc/contexts/SidebarContext.tsx
NotificationContextToast notifications, error messagingsrc/contexts/NotificationContext.tsx

type MenuItem =
| 'dashboard' // Main overview
| 'new-visit' // Create visit
| 'browse-visits' // Visit management
| 'calendar' // Appointments
| 'analytics' // Reports
| 'dictionary' // Medical terms
| 'settings'; // Configuration
  • Deep linking - wsparcie dla appointments/patients
  • Keyboard shortcuts - Ctrl+N (nowa wizyta), Ctrl+F (szukaj)
  • Mobile-responsive sidebar
  • Breadcrumb navigation w zagnieżdżonych widokach

TypKonwencjaPrzykład
Page components*View.tsxSettingsView.tsx
Form components*Form.tsxNewPatientForm.tsx
List components*List.tsxPatientsList.tsx
Editor components*Editor.tsxSOAPEditor.tsx
Modal components*Modal.tsxPasswordResetModal.tsx
components/visits/
├── NewVisitView.tsx # Main view
├── VisitEditor.tsx # Edit form
├── SOAPEditor.tsx # SOAP note editing
├── AudioRecordingSection.tsx # Recording UI
├── TranscriptionViewer.tsx # Transcript display
├── AISuggestionsViewer.tsx # AI suggestions
└── __tests__/ # Component tests

// Przykład użycia Tailwind
<div className="flex flex-col gap-4 p-6 bg-white dark:bg-gray-900 rounded-xl shadow-lg">
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
{title}
</h2>
</div>
// Galaxy theme configuration
interface GalaxyConfig {
starDensity: number; // 0-100 (default: 15)
opacity: number; // 0-1 (default: 0.6)
animationSpeed: number; // 0-10 (default: 2)
enableBlur: boolean; // Glass effect (default: true)
vibrancyMaterial: 'HudWindow' | 'WindowBackground'; // macOS only
}

// Code splitting for major views
const Dashboard = lazy(() => import('./components/dashboard/Dashboard'));
const SettingsView = lazy(() => import('./components/settings/SettingsView'));
const CalendarView = lazy(() => import('./components/calendar/CalendarView'));
// Selective re-rendering with useMemo
const filteredVisits = useMemo(() => {
return visits.filter(visit => matchesFilters(visit, filters));
}, [visits, filters]);
// Debounced search
const debouncedSearch = useDebounce(searchQuery, 300);
// For long lists (patients, visits)
const VisitsList = React.memo(({ visits, filters }) => {
const virtualizedVisits = useVirtualization(visits, {
itemHeight: 120,
overscan: 5,
});
return <VirtualList items={virtualizedVisits} />;
});

// Frontend wywołuje komendy Tauri przez invoke
import { invoke } from '@tauri-apps/api/core';
const createVisit = async (data: CreateVisitRequest) => {
const visit = await invoke<Visit>('create_visit', {
patientId: data.patientId,
userId: data.userId,
visitData: data,
});
return visit;
};
// Wrapper z error handling
export async function safeInvoke<T>(
command: string,
args?: Record<string, unknown>
): Promise<T> {
try {
return await invoke<T>(command, args);
} catch (error) {
console.error(`Command ${command} failed:`, error);
throw new Error(getLocalizedErrorMessage(error));
}
}

  • Vitest - Unit tests, component tests
  • Testing Library - React component testing
  • Playwright - E2E tests
src/
├── __tests__/ # Integration tests
├── components/__tests__/ # Component unit tests
├── hooks/__tests__/ # Hook testing
├── services/__tests__/ # Service logic tests
└── e2e/ # Playwright E2E tests
src/components/visits/__tests__/SOAPEditor.test.tsx
describe('SOAPEditor', () => {
it('should update SOAP fields', async () => {
render(<SOAPEditor visitId="test-id" />);
await userEvent.type(
screen.getByLabelText('Podmiotowo'),
'Patient presented with...'
);
expect(screen.getByLabelText('Podmiotowo')).toHaveValue(
'Patient presented with...'
);
});
});

Streaming audio przechodzi z Tauri invoke na bezpośredni WebSocket do VistaScribe/Libraxis.

Komendy do migracji (engineBridge.ts):

Stara komendaNowe rozwiązanie
recording_start_sessionWebSocket connect
recording_push_chunkWebSocket binary frame
recording_finalize_sessionWebSocket close + ‘end’
transcription_start_streamWebSocket to STT endpoint
transcription_push_chunkWebSocket audio stream

Nowy WebSocket client:

src/services/audio/websocketSTT.ts
interface STTWebSocketConfig {
language: string;
sampleRate: number;
onInterim: (text: string, confidence: number) => void;
onFinal: (text: string, words?: Word[]) => void;
onError: (error: Error) => void;
}
export class STTWebSocketClient {
private ws: WebSocket | null = null;
async connect(config: STTWebSocketConfig): Promise<void> {
const endpoint = import.meta.env.DEV
? 'ws://127.0.0.1:8237/ws/transcribe'
: 'wss://api.libraxis.cloud/stt/v1/stream';
// ...
}
async sendAudio(chunk: ArrayBuffer): Promise<void> {
// Binary frame
}
async close(): Promise<void> {
// Send 'end' and close
}
}

Po zmianach w backend, synchronizuj typy:

Okno terminala
# Sprawdź contract
loctree src src-tauri/src -A --preset-tauri --fail-on-missing-handlers
# Aktualizuj typy
# src/types/api.ts - dodaj/usuń komendy zgodnie z backend

Narzędzia:

Okno terminala
# Nieużywane exporty
pnpm knip --reporter json > knip-report.json
# TypeScript check
pnpm tsc --noEmit
# ESLint
pnpm eslint src/ --ext .ts,.tsx

Ghost event listeners do sprawdzenia:

  • tauri://created - czy używane?
  • vista-devtools:console - czy emitowane?

Obecna struktura (płaska, 50+ hooków) może być zgrupowana:

hooks/
├── common/ # useDebounce, useClickOutside
├── auth/ # useAuth, useBiometricAuth
├── patients/ # usePatientSelection
├── visits/ # useVisitEditor
├── audio/ # (już istnieje)
└── workspace/ # (już istnieje)