- Implemented the symptom database as a full searchable reference area, parallel to terminology.
Added: New medical_symptoms SQLite catalog with 242 seeded symptom records, categories, body systems, tracking notes, urgency-context notes, search terms, and MedlinePlus links: [app/bootstrap.php (line 186)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:186), [seed logic (line 635)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:635) New searchable symptom directory: [public/symptoms.php (line 1)](/Users/tyemeclifford/Documents/GH/workout/public/symptoms.php:1) New dynamic per-symptom page, e.g. /symptom.php?id=24: [public/symptom.php (line 1)](/Users/tyemeclifford/Documents/GH/workout/public/symptom.php:1) Symptom helper/link lookup functions: [app/bootstrap.php (line 1473)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:1473) medicalSymptoms added to the combined library search/API: [app/bootstrap.php (line 2024)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:2024) Symptoms added to nav, homepage, and combined medical search: [site-header.php (line 27)](/Users/tyemeclifford/Documents/GH/workout/public/partials/site-header.php:27), [index.php (line 11)](/Users/tyemeclifford/Documents/GH/workout/public/index.php:11), [medical.php (line 13)](/Users/tyemeclifford/Documents/GH/workout/public/medical.php:13)
This commit is contained in:
@@ -18,14 +18,15 @@ http://127.0.0.1:8080
|
|||||||
|
|
||||||
## Public Routes
|
## Public Routes
|
||||||
|
|
||||||
- `/` - public homepage for the medical tracker service.
|
- `/` - public medical reference hub for medicines, symptoms, terminology, conditions, research, tracker, and admin links.
|
||||||
- `/workouts.php` - condition-tracking feature page.
|
- `/workouts.php` - condition-tracking feature page.
|
||||||
- `/nutrition.php` - medicine catalog feature page.
|
- `/nutrition.php` - medicine catalog feature page.
|
||||||
- `/medicine.php?id=46` - dynamic medicine detail page backed by the SQLite catalog.
|
- `/medicine.php?id=46` - dynamic medicine detail page backed by the SQLite catalog.
|
||||||
|
- `/symptoms.php` - searchable symptom reference database.
|
||||||
|
- `/symptom.php?id=1` - dynamic symptom detail page backed by the SQLite catalog.
|
||||||
- `/recovery.php` - NIH/NLM research links page.
|
- `/recovery.php` - NIH/NLM research links page.
|
||||||
- `/medical.php` - medical terminology, conditions, medicines, and study-link database.
|
- `/medical.php` - medical terminology, conditions, medicines, and study-link database.
|
||||||
- `/term.php?id=1` - dynamic medical-terminology detail page backed by the SQLite catalog.
|
- `/term.php?id=1` - dynamic medical-terminology detail page backed by the SQLite catalog.
|
||||||
- `/signup.php` - public profile signup.
|
|
||||||
- `/tracker.php` - the medical tracker dashboard.
|
- `/tracker.php` - the medical tracker dashboard.
|
||||||
- `/admin.php` - admin user-management dashboard.
|
- `/admin.php` - admin user-management dashboard.
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ The app seeds sample profiles, medical terminology, condition summaries, an expa
|
|||||||
## Included
|
## Included
|
||||||
|
|
||||||
- Multi-user profiles with separate records and theme preferences.
|
- Multi-user profiles with separate records and theme preferences.
|
||||||
- Admin dashboard for creating, editing, activating/deactivating, and deleting user profiles.
|
- Admin/tracker dashboards for creating, editing, activating/deactivating, and deleting user profiles.
|
||||||
- Admin medicine-source updater for refreshing the catalog from RxNorm, DailyMed, and openFDA.
|
- Admin medicine-source updater for refreshing the catalog from RxNorm, DailyMed, and openFDA.
|
||||||
- First user created in a database is automatically promoted to `admin`; later users default to `member`.
|
- First user created in a database is automatically promoted to `admin`; later users default to `member`.
|
||||||
- Daily, weekly, and monthly views for medical logs.
|
- Daily, weekly, and monthly views for medical logs.
|
||||||
@@ -51,6 +52,7 @@ The app seeds sample profiles, medical terminology, condition summaries, an expa
|
|||||||
- Lab result records with units, reference ranges, clinician/source notes, and collection dates.
|
- Lab result records with units, reference ranges, clinician/source notes, and collection dates.
|
||||||
- Symptom journal, allergy list, appointments, and immunization records.
|
- Symptom journal, allergy list, appointments, and immunization records.
|
||||||
- Medical terminology and condition database with source links.
|
- Medical terminology and condition database with source links.
|
||||||
|
- Searchable symptom database with body-system categories, tracking notes, urgency-context notes, and dynamic detail links.
|
||||||
- Searchable medicine catalog by generic name, active ingredient, chemical name, common brand names, and generic/alternate names.
|
- Searchable medicine catalog by generic name, active ingredient, chemical name, common brand names, and generic/alternate names.
|
||||||
- Medicine links to RxNorm, DailyMed, PubChem, MedlinePlus, PubMed, and ClinicalTrials.gov.
|
- Medicine links to RxNorm, DailyMed, PubChem, MedlinePlus, PubMed, and ClinicalTrials.gov.
|
||||||
- NIH/NLM research-link categories related to the listed medicines.
|
- NIH/NLM research-link categories related to the listed medicines.
|
||||||
|
|||||||
+292
-50
@@ -183,6 +183,20 @@ CREATE TABLE IF NOT EXISTS medical_conditions (
|
|||||||
created_at TEXT NOT NULL
|
created_at TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS medical_symptoms (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
body_system TEXT DEFAULT '',
|
||||||
|
plain_language_description TEXT NOT NULL,
|
||||||
|
common_tracking TEXT DEFAULT '',
|
||||||
|
urgency_note TEXT DEFAULT '',
|
||||||
|
source_name TEXT NOT NULL,
|
||||||
|
source_url TEXT NOT NULL,
|
||||||
|
search_terms TEXT DEFAULT '',
|
||||||
|
created_at TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS medication_catalog (
|
CREATE TABLE IF NOT EXISTS medication_catalog (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
generic_name TEXT NOT NULL,
|
generic_name TEXT NOT NULL,
|
||||||
@@ -338,6 +352,8 @@ CREATE INDEX IF NOT EXISTS idx_allergy_records_user ON allergy_records(user_id,
|
|||||||
CREATE INDEX IF NOT EXISTS idx_appointments_user_date ON appointments(user_id, appointment_on);
|
CREATE INDEX IF NOT EXISTS idx_appointments_user_date ON appointments(user_id, appointment_on);
|
||||||
CREATE INDEX IF NOT EXISTS idx_immunization_records_user_date ON immunization_records(user_id, administered_on);
|
CREATE INDEX IF NOT EXISTS idx_immunization_records_user_date ON immunization_records(user_id, administered_on);
|
||||||
CREATE INDEX IF NOT EXISTS idx_medical_terms_term ON medical_terms(term);
|
CREATE INDEX IF NOT EXISTS idx_medical_terms_term ON medical_terms(term);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_medical_symptoms_name ON medical_symptoms(name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_medical_symptoms_category ON medical_symptoms(category);
|
||||||
CREATE INDEX IF NOT EXISTS idx_medication_catalog_generic ON medication_catalog(generic_name);
|
CREATE INDEX IF NOT EXISTS idx_medication_catalog_generic ON medication_catalog(generic_name);
|
||||||
CREATE INDEX IF NOT EXISTS idx_medication_source_updates_created ON medication_source_updates(created_at);
|
CREATE INDEX IF NOT EXISTS idx_medication_source_updates_created ON medication_source_updates(created_at);
|
||||||
SQL);
|
SQL);
|
||||||
@@ -564,6 +580,8 @@ function seed_medical_library(PDO $pdo): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
seed_medical_symptom_catalog($pdo, $created);
|
||||||
|
|
||||||
$medicineCount = (int) $pdo->query('SELECT COUNT(*) FROM medication_catalog')->fetchColumn();
|
$medicineCount = (int) $pdo->query('SELECT COUNT(*) FROM medication_catalog')->fetchColumn();
|
||||||
if ($medicineCount === 0) {
|
if ($medicineCount === 0) {
|
||||||
$medicines = [
|
$medicines = [
|
||||||
@@ -614,6 +632,231 @@ function seed_medical_library(PDO $pdo): void
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function seed_medical_symptom_catalog(PDO $pdo, string $created): void
|
||||||
|
{
|
||||||
|
$rows = symptom_catalog_rows();
|
||||||
|
$existing = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_symptoms');
|
||||||
|
if ($existing >= count($rows)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"INSERT INTO medical_symptoms
|
||||||
|
(name, category, body_system, plain_language_description, common_tracking, urgency_note,
|
||||||
|
source_name, source_url, search_terms, created_at)
|
||||||
|
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM medical_symptoms WHERE lower(name) = lower(?))"
|
||||||
|
);
|
||||||
|
|
||||||
|
$startedTransaction = !$pdo->inTransaction();
|
||||||
|
if ($startedTransaction) {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$stmt->execute([...$row, $created, $row[0]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($startedTransaction) {
|
||||||
|
$pdo->commit();
|
||||||
|
}
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
if ($startedTransaction && $pdo->inTransaction()) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
}
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function symptom_catalog_rows(): array
|
||||||
|
{
|
||||||
|
$groups = [
|
||||||
|
[
|
||||||
|
'General / systemic',
|
||||||
|
'Whole body',
|
||||||
|
'{symptom} is a body-wide symptom or change that can be useful to track alongside temperature, hydration, sleep, medicines, and recent exposures.',
|
||||||
|
'Record onset, duration, severity, temperature if relevant, hydration, recent illness exposure, medicines taken, and what improves or worsens it.',
|
||||||
|
['Fever', 'Chills', 'Fatigue', 'Malaise', 'General weakness', 'Unintentional weight loss', 'Unintentional weight gain', 'Night sweats', 'Excessive sweating', 'Dehydration', 'Excessive thirst', 'Loss of appetite', 'Increased appetite', 'Heat intolerance', 'Cold intolerance', 'Swelling', 'Edema', 'Swollen lymph nodes', 'Body aches'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Pain',
|
||||||
|
'Pain / sensory',
|
||||||
|
'{symptom} is a pain or sensory complaint that is often tracked by location, intensity, timing, triggers, and response to treatment.',
|
||||||
|
'Record pain score, location, character, onset, duration, triggers, injuries, activity limits, medicines used, and associated symptoms.',
|
||||||
|
['Pain', 'Chronic pain', 'Severe pain', 'Abdominal pain', 'Chest pain', 'Chest pressure', 'Back pain', 'Neck pain', 'Joint pain', 'Muscle pain', 'Pelvic pain', 'Flank pain', 'Ear pain', 'Eye pain', 'Sore throat', 'Headache', 'Severe headache', 'Toothache', 'Facial pain', 'Sciatica', 'Cramps', 'Tenderness', 'Burning pain', 'Numbness', 'Tingling'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Respiratory',
|
||||||
|
'Lungs / airways',
|
||||||
|
'{symptom} is a breathing, airway, nose, throat, or lung-related symptom that may be tracked with oxygen saturation, triggers, and medication use when relevant.',
|
||||||
|
'Record breathing effort, oxygen saturation if available, cough character, sputum, fever, triggers, inhaler or medicine use, activity tolerance, and exposure history.',
|
||||||
|
['Cough', 'Dry cough', 'Productive cough', 'Shortness of breath', 'Trouble breathing', 'Wheezing', 'Chest tightness', 'Rapid breathing', 'Shallow breathing', 'Coughing up blood', 'Noisy breathing', 'Snoring', 'Sleep apnea symptoms', 'Nasal congestion', 'Runny nose', 'Sneezing', 'Postnasal drip', 'Sinus pressure', 'Hoarseness', 'Sputum changes'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Cardiovascular',
|
||||||
|
'Heart / circulation',
|
||||||
|
'{symptom} is a heart, blood vessel, or circulation-related symptom that may be useful to track with pulse, blood pressure, swelling, exertion, and medication timing.',
|
||||||
|
'Record pulse, blood pressure if available, activity at onset, duration, swelling location, shortness of breath, chest symptoms, medicines, and whether symptoms occur at rest or with exertion.',
|
||||||
|
['Palpitations', 'Fainting', 'Near-fainting', 'Dizziness', 'Lightheadedness', 'Leg swelling', 'Blue lips', 'Rapid heartbeat', 'Slow heartbeat', 'Orthopnea', 'Exercise intolerance', 'Cold hands and feet', 'Clammy skin'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Digestive',
|
||||||
|
'Gastrointestinal',
|
||||||
|
'{symptom} is a digestive or abdominal symptom that may be tracked with meals, hydration, bowel pattern, medicines, and associated pain.',
|
||||||
|
'Record meals, hydration, bowel frequency, stool appearance, vomiting episodes, abdominal location, fever, weight changes, medicines, and possible food or infection exposures.',
|
||||||
|
['Nausea', 'Vomiting', 'Vomiting blood', 'Diarrhea', 'Constipation', 'Heartburn', 'Indigestion', 'Gas', 'Bloating', 'Belching', 'Abdominal cramps', 'Blood in stool', 'Black stools', 'Rectal bleeding', 'Difficulty swallowing', 'Loss of bowel control', 'Bowel urgency', 'Mucus in stool', 'Jaundice', 'Hiccups', 'Early fullness', 'Loss of taste for food'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Neurologic',
|
||||||
|
'Brain / nerves',
|
||||||
|
'{symptom} is a nervous-system symptom that may be important to track with timing, location, mental status, movement changes, and related medicines or exposures.',
|
||||||
|
'Record onset time, duration, affected body areas, speech or vision changes, balance, weakness, headache, injury, seizure-like activity, medicines, and glucose if relevant.',
|
||||||
|
['Confusion', 'Memory problems', 'Seizure', 'Tremor', 'Migraine aura', 'Vision loss', 'Sudden vision loss', 'Double vision', 'Slurred speech', 'Difficulty speaking', 'Difficulty walking', 'Balance problems', 'Facial droop', 'Weakness on one side', 'Drowsiness', 'Vertigo', 'Loss of consciousness', 'Restless legs', 'Involuntary movements', 'Poor coordination', 'Light sensitivity', 'Sound sensitivity'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Skin / hair / nails',
|
||||||
|
'Skin',
|
||||||
|
'{symptom} is a skin, hair, nail, or visible tissue change that can be tracked with appearance, spread, triggers, itching, pain, and exposures.',
|
||||||
|
'Record appearance, size, color, location, spread, itch or pain, fever, new medicines, foods, soaps, insect exposures, wounds, and photos if useful.',
|
||||||
|
['Rash', 'Itching', 'Hives', 'Bruises', 'Easy bruising', 'Bleeding', 'Wound', 'Blister', 'Skin redness', 'Skin warmth', 'Dry skin', 'Peeling skin', 'Skin color change', 'Cyanosis', 'Hair loss', 'Nail changes', 'Skin ulcer', 'Mole changes', 'Insect bite reaction', 'Pus or drainage', 'Cracked skin', 'Excessive hair growth'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Eye / ear / nose / throat',
|
||||||
|
'Head and neck',
|
||||||
|
'{symptom} is an eye, ear, nose, mouth, or throat symptom that may be tracked with pain, discharge, hearing or vision changes, and infection or allergy context.',
|
||||||
|
'Record side affected, pain, discharge, fever, vision or hearing changes, allergy triggers, recent infections, injuries, dental symptoms, and medicines used.',
|
||||||
|
['Eye redness', 'Eye discharge', 'Watery eyes', 'Blurred vision', 'Floaters', 'Ear discharge', 'Hearing loss', 'Tinnitus', 'Ear fullness', 'Nosebleed', 'Mouth sores', 'Dry mouth', 'Bad breath', 'Swollen gums', 'Gum bleeding', 'Taste changes', 'Smell changes', 'Jaw pain', 'Tongue swelling', 'Difficulty opening mouth'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Urinary / reproductive',
|
||||||
|
'Genitourinary',
|
||||||
|
'{symptom} is a urinary, pelvic, breast, menstrual, or reproductive symptom that may be tracked with timing, pain, bleeding, discharge, and fluid intake.',
|
||||||
|
'Record urination pattern, pain, color, bleeding, discharge, pelvic or flank pain, cycle timing, pregnancy possibility if relevant, sexual exposure concerns, fever, and medicines.',
|
||||||
|
['Painful urination', 'Frequent urination', 'Urgent urination', 'Blood in urine', 'Cloudy urine', 'Urinary incontinence', 'Urinary retention', 'Nighttime urination', 'Decreased urination', 'Vaginal bleeding', 'Vaginal discharge', 'Missed period', 'Heavy menstrual bleeding', 'Painful periods', 'Hot flashes', 'Breast pain', 'Breast lump', 'Nipple discharge', 'Testicular pain', 'Scrotal swelling', 'Erectile dysfunction', 'Pain with sex'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Musculoskeletal',
|
||||||
|
'Bones / joints / muscles',
|
||||||
|
'{symptom} is a muscle, joint, bone, or movement-related symptom that may be tracked with location, activity, injury, swelling, stiffness, and function.',
|
||||||
|
'Record affected area, injury or overuse, swelling, redness, stiffness, range of motion, weakness, pain score, walking changes, falls, medicines, and functional limits.',
|
||||||
|
['Joint swelling', 'Joint stiffness', 'Limited range of motion', 'Muscle weakness', 'Muscle cramps', 'Muscle twitching', 'Back stiffness', 'Gait change', 'Falls', 'Bone pain', 'Shoulder pain', 'Hip pain', 'Knee pain', 'Ankle swelling', 'Hand pain', 'Foot pain', 'Neck stiffness', 'Limping'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Mental health / sleep',
|
||||||
|
'Mood / sleep / behavior',
|
||||||
|
'{symptom} is a mood, behavior, thinking, or sleep-related symptom that can be tracked with timing, stressors, sleep, medicines, substance use, and safety concerns.',
|
||||||
|
'Record mood, sleep hours, triggers, stressors, medicines, substance use, appetite, energy, concentration, safety concerns, and support or care contacts.',
|
||||||
|
['Anxiety', 'Panic attack', 'Depressed mood', 'Mood swings', 'Irritability', 'Insomnia', 'Excessive sleepiness', 'Nightmares', 'Hallucinations', 'Delusions', 'Suicidal thoughts', 'Self-harm thoughts', 'Trouble concentrating', 'Behavior changes', 'Agitation', 'Social withdrawal', 'Low motivation', 'Racing thoughts'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Metabolic / endocrine',
|
||||||
|
'Hormones / metabolism',
|
||||||
|
'{symptom} is a metabolism, hormone, or blood-sugar-related symptom that may be tracked with food intake, glucose readings, medicines, hydration, and energy changes.',
|
||||||
|
'Record meals, hydration, glucose readings if available, medicines, sweating, shaking, urination, thirst, weight changes, energy, and timing around exercise or illness.',
|
||||||
|
['High blood sugar symptoms', 'Low blood sugar symptoms', 'Increased thirst', 'Frequent hunger', 'Excessive urination', 'Shakiness', 'Sweating episodes', 'Unintentional weight changes', 'Feeling overheated', 'Feeling cold', 'Salt craving'],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'Medication / allergy',
|
||||||
|
'Medication safety',
|
||||||
|
'{symptom} is a possible medicine, injection, allergy, or exposure-related symptom that is useful to record with timing, dose, route, and product details.',
|
||||||
|
'Record medicine or exposure name, dose, timing, route, new products, rash or swelling, breathing symptoms, dizziness, stomach symptoms, bleeding, and whether the medicine was stopped or changed.',
|
||||||
|
['Allergic reaction', 'Severe allergic reaction', 'Medication side effects', 'Injection-site reaction', 'Dizziness after medication', 'Nausea after medication', 'Rash after medication', 'Swelling after medication', 'Bleeding after medication', 'Sleepiness after medication'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
$urgentNames = array_fill_keys(array_map('strtolower', [
|
||||||
|
'Chest pain',
|
||||||
|
'Chest pressure',
|
||||||
|
'Shortness of breath',
|
||||||
|
'Trouble breathing',
|
||||||
|
'Coughing up blood',
|
||||||
|
'Blue lips',
|
||||||
|
'Fainting',
|
||||||
|
'Loss of consciousness',
|
||||||
|
'Seizure',
|
||||||
|
'Confusion',
|
||||||
|
'Facial droop',
|
||||||
|
'Weakness on one side',
|
||||||
|
'Slurred speech',
|
||||||
|
'Difficulty speaking',
|
||||||
|
'Sudden vision loss',
|
||||||
|
'Severe headache',
|
||||||
|
'Vomiting blood',
|
||||||
|
'Blood in stool',
|
||||||
|
'Black stools',
|
||||||
|
'Rectal bleeding',
|
||||||
|
'Severe allergic reaction',
|
||||||
|
'Suicidal thoughts',
|
||||||
|
'Self-harm thoughts',
|
||||||
|
]), true);
|
||||||
|
|
||||||
|
$rows = [];
|
||||||
|
foreach ($groups as [$category, $bodySystem, $descriptionTemplate, $tracking, $names]) {
|
||||||
|
foreach ($names as $name) {
|
||||||
|
$urgency = isset($urgentNames[strtolower($name)])
|
||||||
|
? 'Potential urgent symptom: seek emergency care for sudden, severe, new, or worsening symptoms, or when this appears with breathing trouble, chest symptoms, fainting, neurologic changes, severe bleeding, or safety concerns.'
|
||||||
|
: 'Track patterns and contact a clinician for persistent, worsening, unexplained, recurrent, or concerning symptoms.';
|
||||||
|
$searchTerms = strtolower($name . ' ' . $category . ' ' . $bodySystem . ' symptom sign medical tracking medlineplus');
|
||||||
|
$rows[] = [
|
||||||
|
$name,
|
||||||
|
$category,
|
||||||
|
$bodySystem,
|
||||||
|
str_replace('{symptom}', $name, $descriptionTemplate),
|
||||||
|
$tracking,
|
||||||
|
$urgency,
|
||||||
|
'MedlinePlus',
|
||||||
|
symptom_source_url($name),
|
||||||
|
$searchTerms,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function symptom_source_url(string $name): string
|
||||||
|
{
|
||||||
|
$map = [
|
||||||
|
'Abdominal pain' => 'https://medlineplus.gov/abdominalpain.html',
|
||||||
|
'Anxiety' => 'https://medlineplus.gov/anxiety.html',
|
||||||
|
'Back pain' => 'https://medlineplus.gov/backpain.html',
|
||||||
|
'Chest pain' => 'https://medlineplus.gov/chestpain.html',
|
||||||
|
'Constipation' => 'https://medlineplus.gov/constipation.html',
|
||||||
|
'Cough' => 'https://medlineplus.gov/cough.html',
|
||||||
|
'Dehydration' => 'https://medlineplus.gov/dehydration.html',
|
||||||
|
'Depressed mood' => 'https://medlineplus.gov/depression.html',
|
||||||
|
'Diarrhea' => 'https://medlineplus.gov/diarrhea.html',
|
||||||
|
'Dizziness' => 'https://medlineplus.gov/dizzinessandvertigo.html',
|
||||||
|
'Ear pain' => 'https://medlineplus.gov/earinfections.html',
|
||||||
|
'Fatigue' => 'https://medlineplus.gov/fatigue.html',
|
||||||
|
'Fever' => 'https://medlineplus.gov/fever.html',
|
||||||
|
'Hair loss' => 'https://medlineplus.gov/hairloss.html',
|
||||||
|
'Headache' => 'https://medlineplus.gov/headache.html',
|
||||||
|
'Heartburn' => 'https://medlineplus.gov/heartburn.html',
|
||||||
|
'Hives' => 'https://medlineplus.gov/hives.html',
|
||||||
|
'Insomnia' => 'https://medlineplus.gov/insomnia.html',
|
||||||
|
'Itching' => 'https://medlineplus.gov/itching.html',
|
||||||
|
'Jaundice' => 'https://medlineplus.gov/jaundice.html',
|
||||||
|
'Joint pain' => 'https://medlineplus.gov/jointdisorders.html',
|
||||||
|
'Nausea' => 'https://medlineplus.gov/nauseaandvomiting.html',
|
||||||
|
'Neck pain' => 'https://medlineplus.gov/neckinjuriesanddisorders.html',
|
||||||
|
'Pain' => 'https://medlineplus.gov/pain.html',
|
||||||
|
'Palpitations' => 'https://medlineplus.gov/heartdiseases.html',
|
||||||
|
'Rash' => 'https://medlineplus.gov/rashes.html',
|
||||||
|
'Shortness of breath' => 'https://medlineplus.gov/breathingproblems.html',
|
||||||
|
'Sore throat' => 'https://medlineplus.gov/sorethroat.html',
|
||||||
|
'Tinnitus' => 'https://medlineplus.gov/tinnitus.html',
|
||||||
|
'Tremor' => 'https://medlineplus.gov/tremor.html',
|
||||||
|
'Urinary incontinence' => 'https://medlineplus.gov/urinaryincontinence.html',
|
||||||
|
'Vertigo' => 'https://medlineplus.gov/dizzinessandvertigo.html',
|
||||||
|
'Vomiting' => 'https://medlineplus.gov/nauseaandvomiting.html',
|
||||||
|
'Wheezing' => 'https://medlineplus.gov/breathingproblems.html',
|
||||||
|
];
|
||||||
|
|
||||||
|
return $map[$name] ?? 'https://medlineplus.gov/search/?query=' . rawurlencode($name);
|
||||||
|
}
|
||||||
|
|
||||||
function medication_source_url(string $base, string $query): string
|
function medication_source_url(string $base, string $query): string
|
||||||
{
|
{
|
||||||
return $base . rawurlencode($query);
|
return $base . rawurlencode($query);
|
||||||
@@ -1227,6 +1470,11 @@ function term_detail_path(array $term): string
|
|||||||
return '/term.php?id=' . rawurlencode((string) ($term['id'] ?? ''));
|
return '/term.php?id=' . rawurlencode((string) ($term['id'] ?? ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function symptom_detail_path(array $symptom): string
|
||||||
|
{
|
||||||
|
return '/symptom.php?id=' . rawurlencode((string) ($symptom['id'] ?? ''));
|
||||||
|
}
|
||||||
|
|
||||||
function find_medication_catalog_item(PDO $pdo, array $query): ?array
|
function find_medication_catalog_item(PDO $pdo, array $query): ?array
|
||||||
{
|
{
|
||||||
$id = filter_var($query['id'] ?? null, FILTER_VALIDATE_INT);
|
$id = filter_var($query['id'] ?? null, FILTER_VALIDATE_INT);
|
||||||
@@ -1281,6 +1529,29 @@ function find_medical_term_item(PDO $pdo, array $query): ?array
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function find_medical_symptom_item(PDO $pdo, array $query): ?array
|
||||||
|
{
|
||||||
|
$id = filter_var($query['id'] ?? null, FILTER_VALIDATE_INT);
|
||||||
|
if ($id !== false && $id > 0) {
|
||||||
|
return db_one($pdo, 'SELECT * FROM medical_symptoms WHERE id = ?', [$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = trim((string) ($query['name'] ?? $query['symptom'] ?? $query['q'] ?? ''));
|
||||||
|
if ($name === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return db_one(
|
||||||
|
$pdo,
|
||||||
|
"SELECT *
|
||||||
|
FROM medical_symptoms
|
||||||
|
WHERE lower(name) = lower(?)
|
||||||
|
ORDER BY name
|
||||||
|
LIMIT 1",
|
||||||
|
[$name]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function medicine_reference_links(array $medicine): array
|
function medicine_reference_links(array $medicine): array
|
||||||
{
|
{
|
||||||
$links = [
|
$links = [
|
||||||
@@ -1633,6 +1904,7 @@ function state_payload(array $query): array
|
|||||||
);
|
);
|
||||||
$medicalTerms = db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term');
|
$medicalTerms = db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term');
|
||||||
$medicalConditions = db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name');
|
$medicalConditions = db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name');
|
||||||
|
$medicalSymptoms = db_all($pdo, 'SELECT * FROM medical_symptoms ORDER BY category, name');
|
||||||
$medicationCatalog = db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name');
|
$medicationCatalog = db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name');
|
||||||
$researchStudies = db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title');
|
$researchStudies = db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title');
|
||||||
$medicationLogs = db_all(
|
$medicationLogs = db_all(
|
||||||
@@ -1719,6 +1991,7 @@ function state_payload(array $query): array
|
|||||||
'latestMetric' => $latestMetric,
|
'latestMetric' => $latestMetric,
|
||||||
'medicalTerms' => $medicalTerms,
|
'medicalTerms' => $medicalTerms,
|
||||||
'medicalConditions' => $medicalConditions,
|
'medicalConditions' => $medicalConditions,
|
||||||
|
'medicalSymptoms' => $medicalSymptoms,
|
||||||
'medicationCatalog' => $medicationCatalog,
|
'medicationCatalog' => $medicationCatalog,
|
||||||
'researchStudies' => $researchStudies,
|
'researchStudies' => $researchStudies,
|
||||||
'medicationLogs' => $medicationLogs,
|
'medicationLogs' => $medicationLogs,
|
||||||
@@ -1748,6 +2021,7 @@ function library_search_payload(array $query): array
|
|||||||
'query' => '',
|
'query' => '',
|
||||||
'medicalTerms' => db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term LIMIT ?', [$limit]),
|
'medicalTerms' => db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term LIMIT ?', [$limit]),
|
||||||
'medicalConditions' => db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name LIMIT ?', [$limit]),
|
'medicalConditions' => db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name LIMIT ?', [$limit]),
|
||||||
|
'medicalSymptoms' => db_all($pdo, 'SELECT * FROM medical_symptoms ORDER BY category, name LIMIT ?', [$limit]),
|
||||||
'medicationCatalog' => db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name LIMIT ?', [$limit]),
|
'medicationCatalog' => db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name LIMIT ?', [$limit]),
|
||||||
'researchStudies' => db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title LIMIT ?', [$limit]),
|
'researchStudies' => db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title LIMIT ?', [$limit]),
|
||||||
];
|
];
|
||||||
@@ -1756,6 +2030,7 @@ function library_search_payload(array $query): array
|
|||||||
$like = '%' . $search . '%';
|
$like = '%' . $search . '%';
|
||||||
$termParams = array_fill(0, 5, $like);
|
$termParams = array_fill(0, 5, $like);
|
||||||
$conditionParams = array_fill(0, 4, $like);
|
$conditionParams = array_fill(0, 4, $like);
|
||||||
|
$symptomParams = [...array_fill(0, 8, $like), $search, $search . '%', $like];
|
||||||
$medicineParams = array_fill(0, 10, $like);
|
$medicineParams = array_fill(0, 10, $like);
|
||||||
$studyParams = array_fill(0, 6, $like);
|
$studyParams = array_fill(0, 6, $like);
|
||||||
|
|
||||||
@@ -1778,6 +2053,23 @@ function library_search_payload(array $query): array
|
|||||||
LIMIT {$limit}",
|
LIMIT {$limit}",
|
||||||
$conditionParams
|
$conditionParams
|
||||||
),
|
),
|
||||||
|
'medicalSymptoms' => db_all(
|
||||||
|
$pdo,
|
||||||
|
"SELECT * FROM medical_symptoms
|
||||||
|
WHERE name LIKE ? OR category LIKE ? OR body_system LIKE ?
|
||||||
|
OR plain_language_description LIKE ? OR common_tracking LIKE ?
|
||||||
|
OR urgency_note LIKE ? OR source_name LIKE ? OR search_terms LIKE ?
|
||||||
|
ORDER BY
|
||||||
|
CASE
|
||||||
|
WHEN lower(name) = lower(?) THEN 0
|
||||||
|
WHEN name LIKE ? THEN 1
|
||||||
|
WHEN search_terms LIKE ? THEN 2
|
||||||
|
ELSE 3
|
||||||
|
END,
|
||||||
|
category, name
|
||||||
|
LIMIT {$limit}",
|
||||||
|
$symptomParams
|
||||||
|
),
|
||||||
'medicationCatalog' => db_all(
|
'medicationCatalog' => db_all(
|
||||||
$pdo,
|
$pdo,
|
||||||
"SELECT * FROM medication_catalog
|
"SELECT * FROM medication_catalog
|
||||||
@@ -2161,56 +2453,6 @@ function create_user(array $data): array
|
|||||||
return ['user' => db_one($pdo, 'SELECT * FROM users WHERE id = ?', [$userId])];
|
return ['user' => db_one($pdo, 'SELECT * FROM users WHERE id = ?', [$userId])];
|
||||||
}
|
}
|
||||||
|
|
||||||
function public_signup(array $data): array
|
|
||||||
{
|
|
||||||
$pdo = app_db();
|
|
||||||
$name = text_value($data, 'name');
|
|
||||||
if ($name === '') {
|
|
||||||
throw new InvalidArgumentException('Name is required.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$userCount = (int) db_value($pdo, 'SELECT COUNT(*) FROM users');
|
|
||||||
$role = $userCount === 0 ? 'admin' : 'member';
|
|
||||||
$created = app_now();
|
|
||||||
$mode = clamp_choice(text_value($data, 'theme_mode', 'dark'), ['dark', 'light'], 'dark');
|
|
||||||
$accent = clamp_choice(text_value($data, 'theme_accent', 'green'), ['green', 'blue', 'red', 'pink', 'orange'], 'green');
|
|
||||||
|
|
||||||
$stmt = $pdo->prepare(
|
|
||||||
"INSERT INTO users
|
|
||||||
(name, email, fitness_focus, role, status, phone, birthday, height_in, training_level,
|
|
||||||
emergency_contact, theme_mode, theme_accent, updated_at, created_at)
|
|
||||||
VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
|
||||||
);
|
|
||||||
$stmt->execute([
|
|
||||||
$name,
|
|
||||||
text_value($data, 'email'),
|
|
||||||
text_value($data, 'fitness_focus'),
|
|
||||||
$role,
|
|
||||||
text_value($data, 'phone'),
|
|
||||||
text_value($data, 'birthday'),
|
|
||||||
number_value($data, 'height_in'),
|
|
||||||
text_value($data, 'training_level'),
|
|
||||||
text_value($data, 'emergency_contact'),
|
|
||||||
$mode,
|
|
||||||
$accent,
|
|
||||||
$created,
|
|
||||||
$created,
|
|
||||||
]);
|
|
||||||
$userId = (int) $pdo->lastInsertId();
|
|
||||||
|
|
||||||
$goalStmt = $pdo->prepare(
|
|
||||||
"INSERT INTO nutrition_goals
|
|
||||||
(user_id, label, scope, calories, protein_g, carbs_g, fat_g, water_ml, start_date, created_at)
|
|
||||||
VALUES (?, 'Starter day', 'daily', 2400, 150, 260, 75, 3000, ?, ?)"
|
|
||||||
);
|
|
||||||
$goalStmt->execute([$userId, app_date(new DateTimeImmutable('today')), $created]);
|
|
||||||
|
|
||||||
return [
|
|
||||||
'user' => db_one($pdo, 'SELECT * FROM users WHERE id = ?', [$userId]),
|
|
||||||
'first_admin' => $role === 'admin',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
function update_user(array $data): array
|
function update_user(array $data): array
|
||||||
{
|
{
|
||||||
$pdo = app_db();
|
$pdo = app_db();
|
||||||
|
|||||||
Binary file not shown.
@@ -25,7 +25,6 @@ try {
|
|||||||
$data = json_input();
|
$data = json_input();
|
||||||
$routes = [
|
$routes = [
|
||||||
'create_user' => 'create_user',
|
'create_user' => 'create_user',
|
||||||
'public_signup' => 'public_signup',
|
|
||||||
'update_user' => 'update_user',
|
'update_user' => 'update_user',
|
||||||
'delete_user' => 'delete_user',
|
'delete_user' => 'delete_user',
|
||||||
'update_theme' => 'update_theme',
|
'update_theme' => 'update_theme',
|
||||||
|
|||||||
+4
-155
@@ -105,7 +105,6 @@ p {
|
|||||||
|
|
||||||
.site-logo,
|
.site-logo,
|
||||||
.site-nav,
|
.site-nav,
|
||||||
.site-actions,
|
|
||||||
.admin-access-row,
|
.admin-access-row,
|
||||||
.admin-user-main,
|
.admin-user-main,
|
||||||
.admin-badges,
|
.admin-badges,
|
||||||
@@ -206,48 +205,7 @@ p {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-hero {
|
|
||||||
width: min(1440px, calc(100% - 32px));
|
|
||||||
min-height: min(760px, calc(100vh - 44px));
|
|
||||||
margin: 12px auto 0;
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-end;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-hero::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background: linear-gradient(90deg, rgba(8, 9, 13, 0.92), rgba(8, 9, 13, 0.35) 55%, rgba(8, 9, 13, 0.2));
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-hero img {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-hero-copy {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
width: min(780px, 100%);
|
|
||||||
padding: clamp(24px, 6vw, 72px);
|
|
||||||
display: grid;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-lede,
|
|
||||||
.site-hero-copy p:not(.site-eyebrow),
|
|
||||||
.detail-hero p,
|
.detail-hero p,
|
||||||
.signup-copy p,
|
|
||||||
.site-split p,
|
|
||||||
.site-section-heading p {
|
.site-section-heading p {
|
||||||
color: var(--muted-2);
|
color: var(--muted-2);
|
||||||
}
|
}
|
||||||
@@ -259,43 +217,18 @@ p {
|
|||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-actions {
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-credit {
|
|
||||||
position: absolute;
|
|
||||||
right: 14px;
|
|
||||||
bottom: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
max-width: min(460px, calc(100% - 28px));
|
|
||||||
color: rgba(255, 255, 255, 0.78);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 1.35;
|
|
||||||
}
|
|
||||||
|
|
||||||
.photo-credit a,
|
|
||||||
figcaption a {
|
figcaption a {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-band,
|
|
||||||
.site-split,
|
|
||||||
.cta-strip,
|
|
||||||
.detail-hero,
|
.detail-hero,
|
||||||
.detail-grid,
|
.detail-grid,
|
||||||
.signup-layout,
|
|
||||||
.admin-public-shell,
|
.admin-public-shell,
|
||||||
.site-footer {
|
.site-footer {
|
||||||
width: min(1180px, calc(100% - 32px));
|
width: min(1180px, calc(100% - 32px));
|
||||||
margin: 14px auto 0;
|
margin: 14px auto 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-band,
|
|
||||||
.site-split,
|
|
||||||
.cta-strip,
|
|
||||||
.signup-layout,
|
|
||||||
.admin-public-shell,
|
.admin-public-shell,
|
||||||
.site-footer {
|
.site-footer {
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
@@ -321,8 +254,6 @@ figcaption a {
|
|||||||
|
|
||||||
.feature-grid article,
|
.feature-grid article,
|
||||||
.detail-grid article,
|
.detail-grid article,
|
||||||
.site-metric-stack article,
|
|
||||||
.signup-steps article,
|
|
||||||
.term-table article,
|
.term-table article,
|
||||||
.library-row,
|
.library-row,
|
||||||
.record-field,
|
.record-field,
|
||||||
@@ -363,8 +294,6 @@ figcaption a {
|
|||||||
.feature-grid p,
|
.feature-grid p,
|
||||||
.detail-grid p,
|
.detail-grid p,
|
||||||
.term-table p,
|
.term-table p,
|
||||||
.site-metric-stack span,
|
|
||||||
.signup-steps span,
|
|
||||||
.details-line,
|
.details-line,
|
||||||
.timeline-meta {
|
.timeline-meta {
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
@@ -566,7 +495,7 @@ figcaption a {
|
|||||||
|
|
||||||
.library-stats {
|
.library-stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
@@ -589,39 +518,18 @@ figcaption a {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-split,
|
.detail-hero {
|
||||||
.detail-hero,
|
|
||||||
.signup-layout {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.8fr);
|
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.8fr);
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-split > div:first-child,
|
.detail-hero > div {
|
||||||
.detail-hero > div,
|
|
||||||
.signup-copy {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-metric-stack {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-metric-stack article {
|
|
||||||
display: grid;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta-strip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-hero {
|
.detail-hero {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
@@ -647,48 +555,6 @@ figcaption {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signup-card {
|
|
||||||
display: grid;
|
|
||||||
gap: 12px;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
background: color-mix(in srgb, var(--panel-2), transparent 2%);
|
|
||||||
padding: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-steps {
|
|
||||||
display: grid;
|
|
||||||
gap: 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-steps article {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-steps strong {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
color: #06100b;
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-result {
|
|
||||||
min-height: 24px;
|
|
||||||
color: var(--muted-2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-field,
|
.site-field,
|
||||||
.field {
|
.field {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -936,7 +802,7 @@ textarea:focus {
|
|||||||
@media (max-width: 1020px) {
|
@media (max-width: 1020px) {
|
||||||
.site-header,
|
.site-header,
|
||||||
.site-footer,
|
.site-footer,
|
||||||
.cta-strip {
|
.footer-links {
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -951,9 +817,7 @@ textarea:focus {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-split,
|
|
||||||
.detail-hero,
|
.detail-hero,
|
||||||
.signup-layout,
|
|
||||||
.admin-layout,
|
.admin-layout,
|
||||||
.admin-source-form {
|
.admin-source-form {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -966,32 +830,17 @@ textarea:focus {
|
|||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.site-header,
|
.site-header,
|
||||||
.site-hero,
|
|
||||||
.site-band,
|
|
||||||
.site-split,
|
|
||||||
.cta-strip,
|
|
||||||
.detail-hero,
|
.detail-hero,
|
||||||
.detail-grid,
|
.detail-grid,
|
||||||
.signup-layout,
|
|
||||||
.admin-public-shell,
|
.admin-public-shell,
|
||||||
.site-footer {
|
.site-footer {
|
||||||
width: min(100% - 20px, 620px);
|
width: min(100% - 20px, 620px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.site-hero {
|
|
||||||
min-height: 720px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-hero-copy {
|
|
||||||
padding: 22px;
|
|
||||||
padding-bottom: 74px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feature-grid,
|
.feature-grid,
|
||||||
.detail-grid,
|
.detail-grid,
|
||||||
.admin-stats,
|
.admin-stats,
|
||||||
.library-stats,
|
.library-stats,
|
||||||
.signup-row,
|
|
||||||
.form-grid.two {
|
.form-grid.two {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
const siteForm = document.querySelector("#publicSignupForm");
|
|
||||||
const siteResult = document.querySelector("#signupResult");
|
|
||||||
|
|
||||||
function showSignupMessage(message) {
|
|
||||||
if (siteResult) {
|
|
||||||
siteResult.textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function siteApi(action, payload) {
|
|
||||||
const response = await fetch(`/api.php?action=${encodeURIComponent(action)}`, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
const json = await response.json();
|
|
||||||
if (!response.ok || json.error) {
|
|
||||||
throw new Error(json.error || "Request failed.");
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (siteForm) {
|
|
||||||
siteForm.addEventListener("submit", async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const payload = Object.fromEntries(new FormData(siteForm).entries());
|
|
||||||
showSignupMessage("Creating profile...");
|
|
||||||
try {
|
|
||||||
const result = await siteApi("public_signup", payload);
|
|
||||||
if (result.user?.id) {
|
|
||||||
localStorage.setItem("nht:userId", String(result.user.id));
|
|
||||||
}
|
|
||||||
siteForm.reset();
|
|
||||||
const adminCopy = result.first_admin ? " You are the first user, so this profile is an admin." : "";
|
|
||||||
showSignupMessage(`Profile created.${adminCopy} Opening tracker...`);
|
|
||||||
window.setTimeout(() => {
|
|
||||||
window.location.href = "/tracker.php";
|
|
||||||
}, 900);
|
|
||||||
} catch (error) {
|
|
||||||
showSignupMessage(error.message);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
+74
-48
@@ -2,81 +2,107 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
require __DIR__ . '/../app/bootstrap.php';
|
require __DIR__ . '/../app/bootstrap.php';
|
||||||
app_db();
|
$pdo = app_db();
|
||||||
|
|
||||||
$pageTitle = 'Neon Medical Tracker';
|
$pageTitle = 'Medical Reference Library | Neon Medical Tracker';
|
||||||
$activePage = 'home';
|
$activePage = 'home';
|
||||||
$heroImage = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/45/Jogging_Woman_in_Grass.jpg/1280px-Jogging_Woman_in_Grass.jpg';
|
$totalMedicines = (int) db_value($pdo, 'SELECT COUNT(*) FROM medication_catalog');
|
||||||
|
$totalTerms = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_terms');
|
||||||
|
$totalSymptoms = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_symptoms');
|
||||||
|
$totalConditions = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_conditions');
|
||||||
|
$totalStudies = (int) db_value($pdo, 'SELECT COUNT(*) FROM research_studies');
|
||||||
|
|
||||||
require __DIR__ . '/partials/site-header.php';
|
require __DIR__ . '/partials/site-header.php';
|
||||||
?>
|
?>
|
||||||
<main>
|
<main>
|
||||||
<section class="site-hero">
|
<section class="admin-public-shell">
|
||||||
<img src="<?= htmlspecialchars($heroImage, ENT_QUOTES) ?>" alt="Runner training outdoors in bright green grass">
|
<div class="site-section-heading">
|
||||||
<div class="site-hero-copy">
|
<p class="site-eyebrow">Medical reference</p>
|
||||||
<p class="site-eyebrow">Medication logs, vitals, labs, terminology, NIH/NLM research links</p>
|
<h1>Medicines, symptoms, terminology, conditions, and NIH/NLM study links.</h1>
|
||||||
<h1>Neon Medical Tracker</h1>
|
<p>Search the reference database or open the tracker/admin tools for profile-managed medical logs.</p>
|
||||||
<p class="site-lede">A multi-user medical tracking workspace for people who want medicines, readings, labs, symptoms, source links, and profile administration in one web dashboard.</p>
|
|
||||||
<div class="site-actions">
|
|
||||||
<a class="site-primary" href="/signup.php">Start tracking</a>
|
|
||||||
<a class="site-secondary" href="/medical.php">Explore database</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form class="library-search" method="get" action="/medical.php">
|
||||||
|
<label>
|
||||||
|
<span>Search medical library</span>
|
||||||
|
<input name="q" placeholder="Try warfarin, CBC, blood pressure, metformin, asthma">
|
||||||
|
</label>
|
||||||
|
<button class="site-primary" type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="library-stats" aria-label="Library totals">
|
||||||
|
<article><strong><?= number_format($totalMedicines) ?></strong><span>medicines</span></article>
|
||||||
|
<article><strong><?= number_format($totalSymptoms) ?></strong><span>symptoms</span></article>
|
||||||
|
<article><strong><?= number_format($totalTerms) ?></strong><span>terms</span></article>
|
||||||
|
<article><strong><?= number_format($totalConditions) ?></strong><span>conditions</span></article>
|
||||||
|
<article><strong><?= number_format($totalStudies) ?></strong><span>study categories</span></article>
|
||||||
</div>
|
</div>
|
||||||
<p class="photo-credit">
|
|
||||||
Photo: <a href="https://commons.wikimedia.org/wiki/File:Jogging_Woman_in_Grass.jpg">Jogging Woman in Grass</a>
|
|
||||||
by Mike Baird, <a href="https://creativecommons.org/licenses/by/2.0/">CC BY 2.0</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="site-band">
|
<section class="admin-public-shell">
|
||||||
<div class="site-section-heading">
|
<div class="site-section-heading">
|
||||||
<p class="site-eyebrow">Built for daily use</p>
|
<p class="site-eyebrow">Reference sections</p>
|
||||||
<h2>One workspace for personal health records and sourced reference links.</h2>
|
<h2>Open a database area.</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-grid">
|
<div class="feature-grid">
|
||||||
<article>
|
<article>
|
||||||
<span>01</span>
|
<span>Rx</span>
|
||||||
<h3>Medication tracking</h3>
|
<h3>Medicines</h3>
|
||||||
<p>Log prescription and over-the-counter medicines by date, dose, route, frequency, status, prescriber, and notes.</p>
|
<p>Search by generic name, chemical name, active ingredient, brand name, or medication class.</p>
|
||||||
|
<a class="site-secondary" href="/nutrition.php">Open medicines</a>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<span>02</span>
|
<span>ABC</span>
|
||||||
<h3>Vitals and symptoms</h3>
|
<h3>Terminology</h3>
|
||||||
<p>Track blood pressure, pulse, temperature, oxygen saturation, glucose, pain scores, symptoms, and context.</p>
|
<p>Browse plain-language medical terms with clinical context and reference links.</p>
|
||||||
|
<a class="site-secondary" href="/medical.php">Open terminology</a>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<span>03</span>
|
<span>Sx</span>
|
||||||
<h3>Labs and terminology</h3>
|
<h3>Symptoms</h3>
|
||||||
<p>Store lab values and browse plain-language medical terms connected to MedlinePlus, RxNorm, PubMed, and ClinicalTrials.gov.</p>
|
<p>Search symptom names, body systems, tracking notes, and urgency-context references.</p>
|
||||||
|
<a class="site-secondary" href="/symptoms.php">Open symptoms</a>
|
||||||
</article>
|
</article>
|
||||||
<article>
|
<article>
|
||||||
<span>04</span>
|
<span>NIH</span>
|
||||||
<h3>NIH/NLM studies</h3>
|
<h3>Research</h3>
|
||||||
<p>Browse NIH/NLM research categories and study-search links related to the medicines included in the catalog.</p>
|
<p>Open NIH/NLM, PubMed, and ClinicalTrials.gov study categories tied to medicines.</p>
|
||||||
|
<a class="site-secondary" href="/recovery.php">Open research</a>
|
||||||
|
</article>
|
||||||
|
<article>
|
||||||
|
<span>Dx</span>
|
||||||
|
<h3>Conditions</h3>
|
||||||
|
<p>Review condition-tracking topics for vitals, labs, symptoms, medications, and notes.</p>
|
||||||
|
<a class="site-secondary" href="/workouts.php">Open conditions</a>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="site-split">
|
<section class="admin-public-shell">
|
||||||
<div>
|
<div class="site-section-heading">
|
||||||
<p class="site-eyebrow">For households, caregivers, and small teams</p>
|
<p class="site-eyebrow">Profile tools</p>
|
||||||
<h2>Profiles stay separate, admins stay in control.</h2>
|
<h2>Manage medical logs through Tracker and Admin.</h2>
|
||||||
<p>Each user gets a profile, theme preference, medication logs, vitals, lab results, medical notes, and research references. Admins can create users, edit details, activate or deactivate accounts, and remove profiles from a dedicated dashboard.</p>
|
|
||||||
<a class="site-secondary inline-link" href="/admin.php">Open admin dashboard</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="site-metric-stack">
|
<div class="library-list">
|
||||||
<article><strong>Daily</strong><span>Log today’s medicines, readings, symptoms, and notes.</span></article>
|
<article class="library-row">
|
||||||
<article><strong>Weekly</strong><span>Review medication adherence, vital trends, labs, and follow-up context.</span></article>
|
<div class="library-row-main">
|
||||||
<article><strong>Monthly</strong><span>Keep longer-term records ready for visits with clinicians or caregivers.</span></article>
|
<h3>Tracker</h3>
|
||||||
|
<p>Use existing profiles for medications, vitals, labs, symptoms, allergies, appointments, immunizations, and notes.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="library-row-actions">
|
||||||
|
<a class="site-primary" href="/tracker.php">Open tracker</a>
|
||||||
<section class="cta-strip">
|
</div>
|
||||||
<div>
|
</article>
|
||||||
<p class="site-eyebrow">Ready when you are</p>
|
<article class="library-row">
|
||||||
<h2>Create your profile and open the medical tracker.</h2>
|
<div class="library-row-main">
|
||||||
|
<h3>Admin</h3>
|
||||||
|
<p>Create, update, activate, deactivate, and remove profiles from the admin dashboard.</p>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-secondary" href="/admin.php">Manage profiles</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
</div>
|
</div>
|
||||||
<a class="site-primary" href="/signup.php">Sign up</a>
|
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
<?php require __DIR__ . '/partials/site-footer.php'; ?>
|
<?php require __DIR__ . '/partials/site-footer.php'; ?>
|
||||||
|
|||||||
+36
-2
@@ -10,9 +10,11 @@ $searchQuery = trim((string) ($_GET['q'] ?? ''));
|
|||||||
$library = library_search_payload(['q' => $searchQuery, 'limit' => 500]);
|
$library = library_search_payload(['q' => $searchQuery, 'limit' => 500]);
|
||||||
$terms = $library['medicalTerms'];
|
$terms = $library['medicalTerms'];
|
||||||
$conditions = $library['medicalConditions'];
|
$conditions = $library['medicalConditions'];
|
||||||
|
$symptoms = $library['medicalSymptoms'];
|
||||||
$medicines = $library['medicationCatalog'];
|
$medicines = $library['medicationCatalog'];
|
||||||
$studies = $library['researchStudies'];
|
$studies = $library['researchStudies'];
|
||||||
$totalMedicines = (int) db_value($pdo, 'SELECT COUNT(*) FROM medication_catalog');
|
$totalMedicines = (int) db_value($pdo, 'SELECT COUNT(*) FROM medication_catalog');
|
||||||
|
$totalSymptoms = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_symptoms');
|
||||||
|
|
||||||
require __DIR__ . '/partials/site-header.php';
|
require __DIR__ . '/partials/site-header.php';
|
||||||
?>
|
?>
|
||||||
@@ -20,14 +22,14 @@ require __DIR__ . '/partials/site-header.php';
|
|||||||
<section class="admin-public-shell">
|
<section class="admin-public-shell">
|
||||||
<div class="site-section-heading">
|
<div class="site-section-heading">
|
||||||
<p class="site-eyebrow">Searchable medical library</p>
|
<p class="site-eyebrow">Searchable medical library</p>
|
||||||
<h1>Search terms, conditions, medicines, chemical names, and brands.</h1>
|
<h1>Search symptoms, terms, conditions, medicines, chemical names, and brands.</h1>
|
||||||
<p>Entries are concise app-authored summaries with links to NIH/NLM systems such as MedlinePlus, RxNorm, DailyMed, PubChem, PubMed, and ClinicalTrials.gov. This is a tracking/reference aid, not medical advice.</p>
|
<p>Entries are concise app-authored summaries with links to NIH/NLM systems such as MedlinePlus, RxNorm, DailyMed, PubChem, PubMed, and ClinicalTrials.gov. This is a tracking/reference aid, not medical advice.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="library-search" method="get" action="/medical.php">
|
<form class="library-search" method="get" action="/medical.php">
|
||||||
<label>
|
<label>
|
||||||
<span>Search medical library</span>
|
<span>Search medical library</span>
|
||||||
<input name="q" value="<?= htmlspecialchars($searchQuery, ENT_QUOTES) ?>" placeholder="Try Lipitor, atorvastatin, blood pressure, CBC, metformin">
|
<input name="q" value="<?= htmlspecialchars($searchQuery, ENT_QUOTES) ?>" placeholder="Try chest pain, rash, Lipitor, blood pressure, CBC, metformin">
|
||||||
</label>
|
</label>
|
||||||
<button class="site-primary" type="submit">Search</button>
|
<button class="site-primary" type="submit">Search</button>
|
||||||
<?php if ($searchQuery !== ''): ?>
|
<?php if ($searchQuery !== ''): ?>
|
||||||
@@ -37,6 +39,7 @@ require __DIR__ . '/partials/site-header.php';
|
|||||||
|
|
||||||
<div class="library-stats" aria-label="Search result counts">
|
<div class="library-stats" aria-label="Search result counts">
|
||||||
<article><strong><?= count($medicines) ?></strong><span><?= $searchQuery === '' ? 'medicines shown of ' . number_format($totalMedicines) : 'medicine results' ?></span></article>
|
<article><strong><?= count($medicines) ?></strong><span><?= $searchQuery === '' ? 'medicines shown of ' . number_format($totalMedicines) : 'medicine results' ?></span></article>
|
||||||
|
<article><strong><?= count($symptoms) ?></strong><span><?= $searchQuery === '' ? 'symptoms shown of ' . number_format($totalSymptoms) : 'symptom results' ?></span></article>
|
||||||
<article><strong><?= count($terms) ?></strong><span>terms</span></article>
|
<article><strong><?= count($terms) ?></strong><span>terms</span></article>
|
||||||
<article><strong><?= count($conditions) ?></strong><span>conditions</span></article>
|
<article><strong><?= count($conditions) ?></strong><span>conditions</span></article>
|
||||||
<article><strong><?= count($studies) ?></strong><span>study links</span></article>
|
<article><strong><?= count($studies) ?></strong><span>study links</span></article>
|
||||||
@@ -61,6 +64,37 @@ require __DIR__ . '/partials/site-header.php';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-public-shell">
|
||||||
|
<div class="site-section-heading">
|
||||||
|
<p class="site-eyebrow">Symptoms</p>
|
||||||
|
<h2>Symptom reference records</h2>
|
||||||
|
</div>
|
||||||
|
<?php if (!$symptoms): ?>
|
||||||
|
<article class="empty-state">
|
||||||
|
<h3>No symptoms found.</h3>
|
||||||
|
<p>Try a body system, symptom name, sign, or tracking phrase.</p>
|
||||||
|
</article>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="library-list">
|
||||||
|
<?php foreach ($symptoms as $symptom): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars((string) $symptom['name'], ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) $symptom['plain_language_description'], ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $symptom['category'], ENT_QUOTES) ?></span>
|
||||||
|
<span><?= htmlspecialchars((string) ($symptom['body_system'] ?: 'Symptom'), ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-primary" href="<?= htmlspecialchars(symptom_detail_path($symptom), ENT_QUOTES) ?>">View symptom</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="admin-public-shell">
|
<section class="admin-public-shell">
|
||||||
<div class="site-section-heading">
|
<div class="site-section-heading">
|
||||||
<p class="site-eyebrow">Terminology</p>
|
<p class="site-eyebrow">Terminology</p>
|
||||||
|
|||||||
@@ -4,11 +4,9 @@
|
|||||||
<p>Medication logs, vitals, lab results, terminology, NIH/NLM study links, and admin-managed profiles.</p>
|
<p>Medication logs, vitals, lab results, terminology, NIH/NLM study links, and admin-managed profiles.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="/signup.php">Create profile</a>
|
|
||||||
<a href="/tracker.php">Open medical tracker</a>
|
<a href="/tracker.php">Open medical tracker</a>
|
||||||
<a href="/admin.php">Admin dashboard</a>
|
<a href="/admin.php">Admin dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="/assets/site.js" defer></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ function site_active(string $page, string $activePage): string
|
|||||||
<a<?= site_active('home', $activePage) ?> href="/">Home</a>
|
<a<?= site_active('home', $activePage) ?> href="/">Home</a>
|
||||||
<a<?= site_active('conditions', $activePage) ?> href="/workouts.php">Conditions</a>
|
<a<?= site_active('conditions', $activePage) ?> href="/workouts.php">Conditions</a>
|
||||||
<a<?= site_active('medicines', $activePage) ?> href="/nutrition.php">Medicines</a>
|
<a<?= site_active('medicines', $activePage) ?> href="/nutrition.php">Medicines</a>
|
||||||
|
<a<?= site_active('symptoms', $activePage) ?> href="/symptoms.php">Symptoms</a>
|
||||||
<a<?= site_active('research', $activePage) ?> href="/recovery.php">Research</a>
|
<a<?= site_active('research', $activePage) ?> href="/recovery.php">Research</a>
|
||||||
<a<?= site_active('terminology', $activePage) ?> href="/medical.php">Terminology</a>
|
<a<?= site_active('terminology', $activePage) ?> href="/medical.php">Terminology</a>
|
||||||
<a<?= site_active('signup', $activePage) ?> href="/signup.php">Sign up</a>
|
|
||||||
<a href="/tracker.php">Tracker</a>
|
<a href="/tracker.php">Tracker</a>
|
||||||
<a href="/admin.php">Admin</a>
|
<a href="/admin.php">Admin</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|||||||
+2
-44
@@ -1,47 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
require __DIR__ . '/../app/bootstrap.php';
|
header('Location: /tracker.php', true, 302);
|
||||||
app_db();
|
exit;
|
||||||
|
|
||||||
$pageTitle = 'Sign Up | Neon Medical Tracker';
|
|
||||||
$activePage = 'signup';
|
|
||||||
require __DIR__ . '/partials/site-header.php';
|
|
||||||
?>
|
|
||||||
<main>
|
|
||||||
<section class="signup-layout">
|
|
||||||
<div class="signup-copy">
|
|
||||||
<p class="site-eyebrow">Create your profile</p>
|
|
||||||
<h1>Start with the basics. Add records as care changes.</h1>
|
|
||||||
<p>Your profile keeps medications, vitals, labs, symptoms, allergies, appointments, vaccines, notes, and theme preferences separate from every other user.</p>
|
|
||||||
<div class="signup-steps">
|
|
||||||
<article><strong>1</strong><span>Create profile</span></article>
|
|
||||||
<article><strong>2</strong><span>Open tracker</span></article>
|
|
||||||
<article><strong>3</strong><span>Add medical records</span></article>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form class="signup-card" id="publicSignupForm">
|
|
||||||
<div>
|
|
||||||
<p class="site-eyebrow">Sign up</p>
|
|
||||||
<h2>New profile</h2>
|
|
||||||
</div>
|
|
||||||
<label class="site-field"><span>Name</span><input name="name" required autocomplete="name"></label>
|
|
||||||
<label class="site-field"><span>Email</span><input name="email" type="email" autocomplete="email"></label>
|
|
||||||
<label class="site-field"><span>Phone</span><input name="phone" type="tel" autocomplete="tel"></label>
|
|
||||||
<label class="site-field"><span>Care focus</span><input name="fitness_focus" placeholder="Blood pressure, glucose, asthma"></label>
|
|
||||||
<div class="signup-row">
|
|
||||||
<label class="site-field"><span>Birthday</span><input name="birthday" type="date"></label>
|
|
||||||
<label class="site-field"><span>Height in</span><input name="height_in" type="number" min="0" step="0.5"></label>
|
|
||||||
</div>
|
|
||||||
<label class="site-field"><span>Tracking comfort</span><select name="training_level"><option></option><option>Getting started</option><option>Comfortable</option><option>Advanced</option><option>Caregiver</option></select></label>
|
|
||||||
<div class="signup-row">
|
|
||||||
<label class="site-field"><span>Theme</span><select name="theme_mode"><option>dark</option><option>light</option></select></label>
|
|
||||||
<label class="site-field"><span>Accent</span><select name="theme_accent"><option>green</option><option>blue</option><option>red</option><option>pink</option><option>orange</option></select></label>
|
|
||||||
</div>
|
|
||||||
<button class="site-primary full" type="submit">Create profile</button>
|
|
||||||
<p class="signup-result" id="signupResult" role="status" aria-live="polite"></p>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<?php require __DIR__ . '/partials/site-footer.php'; ?>
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/../app/bootstrap.php';
|
||||||
|
$pdo = app_db();
|
||||||
|
|
||||||
|
$symptom = find_medical_symptom_item($pdo, $_GET);
|
||||||
|
$pageTitle = $symptom
|
||||||
|
? (string) $symptom['name'] . ' | Symptom Detail | Neon Medical Tracker'
|
||||||
|
: 'Symptom Not Found | Neon Medical Tracker';
|
||||||
|
$activePage = 'symptoms';
|
||||||
|
|
||||||
|
$relatedTerms = [];
|
||||||
|
$relatedConditions = [];
|
||||||
|
$relatedMedicines = [];
|
||||||
|
$relatedStudies = [];
|
||||||
|
if ($symptom) {
|
||||||
|
$related = library_search_payload(['q' => (string) $symptom['name'], 'limit' => 8]);
|
||||||
|
$relatedTerms = $related['medicalTerms'];
|
||||||
|
$relatedConditions = $related['medicalConditions'];
|
||||||
|
$relatedMedicines = $related['medicationCatalog'];
|
||||||
|
$relatedStudies = $related['researchStudies'];
|
||||||
|
} else {
|
||||||
|
http_response_code(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
require __DIR__ . '/partials/site-header.php';
|
||||||
|
?>
|
||||||
|
<main>
|
||||||
|
<?php if (!$symptom): ?>
|
||||||
|
<section class="admin-public-shell record-shell">
|
||||||
|
<p class="site-eyebrow">Symptom database</p>
|
||||||
|
<h1>Symptom not found.</h1>
|
||||||
|
<p>The requested symptom link does not match a current database record.</p>
|
||||||
|
<a class="site-primary" href="/symptoms.php">Search symptoms</a>
|
||||||
|
</section>
|
||||||
|
<?php else: ?>
|
||||||
|
<section class="admin-public-shell record-shell">
|
||||||
|
<div class="record-heading">
|
||||||
|
<div>
|
||||||
|
<p class="site-eyebrow">Symptom detail</p>
|
||||||
|
<h1><?= htmlspecialchars((string) $symptom['name'], ENT_QUOTES) ?></h1>
|
||||||
|
<p><?= htmlspecialchars((string) $symptom['plain_language_description'], ENT_QUOTES) ?></p>
|
||||||
|
</div>
|
||||||
|
<div class="record-actions">
|
||||||
|
<a class="site-primary" href="/tracker.php">Open tracker</a>
|
||||||
|
<a class="site-secondary" href="/symptoms.php?q=<?= rawurlencode((string) $symptom['name']) ?>">Symptom search</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="record-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $symptom['category'], ENT_QUOTES) ?></span>
|
||||||
|
<span><?= htmlspecialchars((string) ($symptom['body_system'] ?: 'Symptom'), ENT_QUOTES) ?></span>
|
||||||
|
<span>ID <?= htmlspecialchars((string) $symptom['id'], ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-public-shell">
|
||||||
|
<div class="record-detail-grid">
|
||||||
|
<article class="record-field wide">
|
||||||
|
<span>Common Tracking</span>
|
||||||
|
<p><?= htmlspecialchars((string) $symptom['common_tracking'], ENT_QUOTES) ?></p>
|
||||||
|
</article>
|
||||||
|
<article class="record-field wide">
|
||||||
|
<span>Urgency Context</span>
|
||||||
|
<p><?= htmlspecialchars((string) $symptom['urgency_note'], ENT_QUOTES) ?></p>
|
||||||
|
</article>
|
||||||
|
<article class="record-field wide">
|
||||||
|
<span>Source</span>
|
||||||
|
<div class="source-links">
|
||||||
|
<a href="<?= htmlspecialchars((string) $symptom['source_url'], ENT_QUOTES) ?>"><?= htmlspecialchars((string) $symptom['source_name'], ENT_QUOTES) ?></a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<?php if ($relatedTerms || $relatedConditions || $relatedMedicines || $relatedStudies): ?>
|
||||||
|
<section class="admin-public-shell">
|
||||||
|
<div class="site-section-heading">
|
||||||
|
<p class="site-eyebrow">Connected records</p>
|
||||||
|
<h2>Database matches for this symptom</h2>
|
||||||
|
</div>
|
||||||
|
<div class="library-list">
|
||||||
|
<?php foreach ($relatedTerms as $term): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars((string) $term['term'], ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) $term['plain_language_definition'], ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $term['category'], ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-secondary" href="<?= htmlspecialchars(term_detail_path($term), ENT_QUOTES) ?>">View term</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php foreach ($relatedConditions as $condition): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars((string) $condition['name'], ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) $condition['overview'], ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $condition['category'], ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-secondary" href="<?= htmlspecialchars((string) $condition['source_url'], ENT_QUOTES) ?>">Source</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php foreach ($relatedMedicines as $medicine): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars(ucfirst((string) $medicine['generic_name']), ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) ($medicine['common_use'] ?: $medicine['class_name']), ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) ($medicine['class_name'] ?: 'Medication'), ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-secondary" href="<?= htmlspecialchars(medicine_detail_path($medicine), ENT_QUOTES) ?>">View medicine</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
|
<?php foreach ($relatedStudies as $study): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars((string) $study['title'], ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) $study['summary'], ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $study['category'], ENT_QUOTES) ?></span>
|
||||||
|
<span><?= htmlspecialchars((string) $study['related_medicine'], ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-secondary" href="<?= htmlspecialchars((string) $study['source_url'], ENT_QUOTES) ?>">Study link</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
</main>
|
||||||
|
<?php require __DIR__ . '/partials/site-footer.php'; ?>
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/../app/bootstrap.php';
|
||||||
|
$pdo = app_db();
|
||||||
|
|
||||||
|
$pageTitle = 'Symptom Database | Neon Medical Tracker';
|
||||||
|
$activePage = 'symptoms';
|
||||||
|
$searchQuery = trim((string) ($_GET['q'] ?? ''));
|
||||||
|
$library = library_search_payload(['q' => $searchQuery, 'limit' => 500]);
|
||||||
|
$symptoms = $library['medicalSymptoms'];
|
||||||
|
$totalSymptoms = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_symptoms');
|
||||||
|
|
||||||
|
require __DIR__ . '/partials/site-header.php';
|
||||||
|
?>
|
||||||
|
<main>
|
||||||
|
<section class="admin-public-shell">
|
||||||
|
<div class="site-section-heading">
|
||||||
|
<p class="site-eyebrow">Symptom database</p>
|
||||||
|
<h1>Search symptoms by name, body system, tracking context, and urgency notes.</h1>
|
||||||
|
<p>These entries are reference summaries with MedlinePlus source links. They support tracking and discussion, not diagnosis or treatment decisions.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="library-search" method="get" action="/symptoms.php">
|
||||||
|
<label>
|
||||||
|
<span>Search symptoms</span>
|
||||||
|
<input name="q" value="<?= htmlspecialchars($searchQuery, ENT_QUOTES) ?>" placeholder="Try chest pain, rash, nausea, dizziness, urinary, sleep">
|
||||||
|
</label>
|
||||||
|
<button class="site-primary" type="submit">Search</button>
|
||||||
|
<?php if ($searchQuery !== ''): ?>
|
||||||
|
<a class="site-secondary" href="/symptoms.php">Clear</a>
|
||||||
|
<?php endif; ?>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="library-stats" aria-label="Symptom result count">
|
||||||
|
<article><strong><?= count($symptoms) ?></strong><span><?= $searchQuery === '' ? 'shown of ' . number_format($totalSymptoms) . ' symptoms' : 'symptom results' ?></span></article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="admin-public-shell">
|
||||||
|
<div class="site-section-heading">
|
||||||
|
<p class="site-eyebrow">Catalog directory</p>
|
||||||
|
<h2>Symptom records</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (!$symptoms): ?>
|
||||||
|
<article class="empty-state">
|
||||||
|
<h3>No symptoms found.</h3>
|
||||||
|
<p>Try a body system, symptom name, sign, or tracking phrase.</p>
|
||||||
|
</article>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="library-list">
|
||||||
|
<?php foreach ($symptoms as $symptom): ?>
|
||||||
|
<article class="library-row">
|
||||||
|
<div class="library-row-main">
|
||||||
|
<h3><?= htmlspecialchars((string) $symptom['name'], ENT_QUOTES) ?></h3>
|
||||||
|
<p><?= htmlspecialchars((string) $symptom['plain_language_description'], ENT_QUOTES) ?></p>
|
||||||
|
<div class="library-row-meta">
|
||||||
|
<span><?= htmlspecialchars((string) $symptom['category'], ENT_QUOTES) ?></span>
|
||||||
|
<span><?= htmlspecialchars((string) ($symptom['body_system'] ?: 'Symptom'), ENT_QUOTES) ?></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="library-row-actions">
|
||||||
|
<a class="site-primary" href="<?= htmlspecialchars(symptom_detail_path($symptom), ENT_QUOTES) ?>">View symptom</a>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?php require __DIR__ . '/partials/site-footer.php'; ?>
|
||||||
+1
-1
@@ -16,7 +16,7 @@ require __DIR__ . '/partials/site-header.php';
|
|||||||
<p class="site-eyebrow">Condition tracking</p>
|
<p class="site-eyebrow">Condition tracking</p>
|
||||||
<h1>Track readings, symptoms, labs, and medications by condition.</h1>
|
<h1>Track readings, symptoms, labs, and medications by condition.</h1>
|
||||||
<p>Use structured logs for blood pressure, glucose, pain, oxygen, temperature, lab results, medications, and notes. The condition library links back to NIH/NLM patient-information sources.</p>
|
<p>Use structured logs for blood pressure, glucose, pain, oxygen, temperature, lab results, medications, and notes. The condition library links back to NIH/NLM patient-information sources.</p>
|
||||||
<a class="site-primary" href="/signup.php">Create a profile</a>
|
<a class="site-primary" href="/tracker.php">Open tracker</a>
|
||||||
</div>
|
</div>
|
||||||
<figure>
|
<figure>
|
||||||
<img src="<?= htmlspecialchars($photo, ENT_QUOTES) ?>" alt="People cycling outdoors">
|
<img src="<?= htmlspecialchars($photo, ENT_QUOTES) ?>" alt="People cycling outdoors">
|
||||||
|
|||||||
Reference in New Issue
Block a user