Ruch Manual: Gabinet Fizjoterapii
Strona gabinetu, na której pacjent sam wskazuje, co go boli
To projekt demonstracyjny, nie wdrożenie u klienta. Zbudowałem go, żeby pokazać ten mechanizm w działaniu.
W skrócie
Dla kogo
Gabinety i praktyki, w których pacjent nie wie, której usługi potrzebuje: fizjoterapia, rehabilitacja, stomatologia, kosmetologia.
Problem
Pacjent z bólem kolana wchodzi na listę usług i musi sam zgadnąć, czy potrzebuje kinesiotapingu, suchego igłowania czy terapii manualnej. Część zamyka stronę i dzwoni gdzie indziej.
Co to zmienia
Klika miejsce, które boli, na rysunku ciała. Strona pokazuje pasujące usługi i wypełnia za niego formularz rezerwacji. Nie musi znać nazw zabiegów.
Strona gabinetu, na której pacjent sam wskazuje, co go boli
Standardowe strony gabinetów fizjoterapii prezentują listę usług, pozostawiając pacjentowi decyzję, które z nich dotyczą jego problemu. To tworzy tarcie: pacjent z bólem kolana musi sam rozpoznać, czy potrzebuje kineziotapingu, suchego igłowania, czy rehabilitacji pooperacyjnej. Ruch Manual rozwiązuje ten problem odwrotnie, zaczyna od symptomu. Interaktywna figura anatomiczna przyjmuje wielokrotny wybór stref bólu, a system automatycznie filtruje i rankuje usługi pod kątem dopasowania do wybranej kombinacji. Wynik jest przekazywany bezpośrednio do modalu rezerwacji jako pre-selekcja, redukując liczbę kroków do decyzji zakupowej.
Co z tego wynika dla Twojej firmy
Ten wzorzec przenosi się na każdą praktykę z listą usług dłuższą niż pięć pozycji. Pacjent nie musi znać się na nazwach zabiegów, żeby umówić wizytę, a Ty dostajesz zgłoszenie z opisanym problemem zamiast wiadomości „dzień dobry, bolą mnie plecy”. Techniczna strona rzeczy jest tu drugorzędna: liczy się to, że decyzja, która wcześniej stała po stronie pacjenta, przeszła na stronę.
DLA ZAINTERESOWANYCH TECHNICZNIE Na czym to zbudowałem i dlaczego akurat tak. Kliknij, żeby rozwinąć.
Stack technologiczny
React 18 (funkcyjny, single-component)
Cały interfejs to jeden eksportowany komponent RuchManual, bez routera i bez zewnętrznej biblioteki stanu. Decyzja świadoma: projekt to landing page z modalem, nie SPA. Nadmiarowe warstwy abstrakcji byłyby technicznym długiem.
useMemo jako silnik rekomendacji
Funkcja scored rekomputuje ranking usług przy każdej zmianie selParts. Iteruje po macierzy relevance_score (wartości 1–3 per strefa ciała per usługa), sumuje wyniki i sortuje po covered (pokrycie stref), a potem po score (ważona suma). Deterministyczny algorytm triage bez ML, typowany danymi w stałej SVCS.
useCallback na handlerze togglePart
Selektywna memoizacja handlera SVG zapobiega zbędnemu re-renderowi 13 nakładek interaktywnych przy każdej zmianie stanu rodzica.
IntersectionObserver API (bez biblioteki)
Zamiast react-intersection-observer czy framer-motion, scroll-animacje obsługiwane natywnie: useEffect rejestruje observer z threshold: 0.08, który aktualizuje słownik vis (id → boolean). Klasy CSS generowane dynamicznie przez <style>{visStyle}</style>, deklaratywność bez manipulacji DOM.
SVG data-driven: 13 stref interaktywnych
SVG_ZONES to czysta tablica obiektów opisujących typ elementu SVG (ellipse, circle, rect) i parametry geometryczne. Renderowane przez <El {...p} /> z dynamicznie obliczonym fill/stroke na podstawie stanu. Dodanie nowej strefy to jeden obiekt w tablicy.
Base64 asset embedding + CSS-in-JS
Cztery obrazy i logo SVG inlinowane jako Data URI, zero zewnętrznych żądań HTTP po załadowaniu. Cały system wizualny oparty na słowniku tokenów C i jednym bloku <style> z keyframesami. clamp() do responsywności bez breakpointów mediowych w logice JS.
Najciekawsze rozwiązania techniczne
Macierz trafności usług jako model danych
Każdy rekord w SVCS zawiera pole map: Record<BodyPartId, 1|2|3>, słownik mapujący strefę ciała na wagę trafności. Funkcja scored oblicza: covered (ile stref pokryte), score (suma wag), pct (covered/selParts.length) dla paska postępu. Wynik to posortowana lista do 5 usług z animowanym wskaźnikiem trafności.
Wizard rezerwacji jako maszyna stanów opartych na jednej zmiennej step
Modal 4-krokowy (step ∈ {1,2,3,4}) z logiką kontekstową: wywołanie openBook(svcId, thId) z Body-Map pre-wypełnia selSvc/selTh i ustawia step:2, pomijając krok wyboru usługi. closeBook resetuje stan z opóźnieniem 280ms (czas animacji zamknięcia) zapobiegając flashowi pustego formularza.
Animacje scroll bez biblioteki zewnętrznej
Zamiast framer-motion (200+ kB), system animacji to 12-liniowy useEffect z IntersectionObserver i akumulatorem vis (mapa id→boolean). Klasy .fade-in.vis aplikowane przez dynamicznie generowany blok <style>, separacja logiki JS od CSS bez imperatywnych mutacji classList.
Twoja firma może być
moim kolejnym projektem
Opowiedz mi o swojej firmie, a powiem, który z tych mechanizmów miałby u Ciebie sens, a który byłby wydatkiem bez pokrycia.
Powiem też, czego nie warto u Ciebie robić