diff --git a/README.md b/README.md index a9e66a6..3c5d4dc 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,10 @@ http://127.0.0.1:8080 - `/` - public homepage for the medical tracker service. - `/workouts.php` - condition-tracking feature page. - `/nutrition.php` - medicine catalog feature page. +- `/medicine.php?id=46` - dynamic medicine detail page backed by the SQLite catalog. - `/recovery.php` - NIH/NLM research links page. - `/medical.php` - medical terminology, conditions, medicines, and study-link database. +- `/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. - `/admin.php` - admin user-management dashboard. diff --git a/app/bootstrap.php b/app/bootstrap.php index da6a04d..2402448 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -1217,6 +1217,84 @@ function db_value(PDO $pdo, string $sql, array $params = []): mixed return $stmt->fetchColumn(); } +function medicine_detail_path(array $medicine): string +{ + return '/medicine.php?id=' . rawurlencode((string) ($medicine['id'] ?? '')); +} + +function term_detail_path(array $term): string +{ + return '/term.php?id=' . rawurlencode((string) ($term['id'] ?? '')); +} + +function find_medication_catalog_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 medication_catalog WHERE id = ?', [$id]); + } + + $name = trim((string) ($query['name'] ?? $query['q'] ?? '')); + if ($name === '') { + return null; + } + + return db_one( + $pdo, + "SELECT * + FROM medication_catalog + WHERE lower(generic_name) = lower(?) + OR lower(active_ingredient) = lower(?) + OR lower(chemical_name) = lower(?) + ORDER BY + CASE + WHEN lower(generic_name) = lower(?) THEN 0 + WHEN lower(active_ingredient) = lower(?) THEN 1 + ELSE 2 + END, + generic_name + LIMIT 1", + [$name, $name, $name, $name, $name] + ); +} + +function find_medical_term_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_terms WHERE id = ?', [$id]); + } + + $term = trim((string) ($query['term'] ?? $query['q'] ?? '')); + if ($term === '') { + return null; + } + + return db_one( + $pdo, + "SELECT * + FROM medical_terms + WHERE lower(term) = lower(?) + ORDER BY term + LIMIT 1", + [$term] + ); +} + +function medicine_reference_links(array $medicine): array +{ + $links = [ + 'RxNorm' => $medicine['rxnorm_url'] ?? '', + 'PubChem' => $medicine['pubchem_url'] ?? '', + 'DailyMed' => $medicine['dailymed_url'] ?? '', + 'MedlinePlus' => $medicine['medlineplus_url'] ?? '', + 'PubMed' => $medicine['pubmed_url'] ?? '', + 'Studies' => $medicine['clinicaltrials_url'] ?? '', + ]; + + return array_filter($links, static fn ($url) => trim((string) $url) !== ''); +} + function db_key_values(PDO $pdo, string $sql, array $params = []): array { $rows = db_all($pdo, $sql, $params); diff --git a/public/assets/site.css b/public/assets/site.css index 79c0bfd..0925adb 100644 --- a/public/assets/site.css +++ b/public/assets/site.css @@ -324,6 +324,9 @@ figcaption a { .site-metric-stack article, .signup-steps article, .term-table article, +.library-row, +.record-field, +.empty-state, .admin-denied, .admin-stat, .admin-user-card, @@ -338,6 +341,18 @@ figcaption a { min-height: 190px; } +.feature-grid article, +.detail-grid article, +.term-table article, +.library-row, +.record-field, +.empty-state, +.source-links, +.library-row-main { + min-width: 0; + overflow-wrap: anywhere; +} + .feature-grid span { display: inline-flex; color: var(--accent); @@ -390,8 +405,130 @@ figcaption a { .source-links a { border: 1px solid var(--line); border-radius: 999px; + max-width: 100%; padding: 4px 8px; font-size: 0.78rem; + white-space: normal; + overflow-wrap: anywhere; +} + +.library-list { + display: grid; + gap: 10px; +} + +.library-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 14px; + align-items: start; +} + +.library-row-main { + display: grid; + gap: 7px; +} + +.library-row-main h3 { + line-height: 1.25; + overflow-wrap: anywhere; +} + +.library-row-main p, +.record-field p, +.empty-state p { + color: var(--muted); + font-size: 0.9rem; +} + +.library-row-meta, +.record-meta, +.record-actions, +.library-row-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.library-row-meta { + min-width: 0; +} + +.library-row-meta span, +.record-meta span { + max-width: 100%; + border: 1px solid var(--line); + border-radius: 999px; + color: var(--muted-2); + background: rgba(255, 255, 255, 0.035); + padding: 5px 8px; + font-size: 0.76rem; + line-height: 1.25; + overflow-wrap: anywhere; +} + +.library-row-actions { + justify-content: flex-end; + min-width: 0; +} + +.record-shell { + display: grid; + gap: 16px; +} + +.record-heading { + display: flex; + justify-content: space-between; + gap: 18px; + align-items: flex-start; +} + +.record-heading > div:first-child { + display: grid; + gap: 10px; + min-width: 0; + max-width: 820px; +} + +.record-heading h1 { + overflow-wrap: anywhere; +} + +.record-actions { + justify-content: flex-end; +} + +.record-meta { + min-width: 0; +} + +.record-detail-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; +} + +.record-field { + display: grid; + gap: 8px; +} + +.record-field.wide { + grid-column: 1 / -1; +} + +.record-field > span { + color: var(--accent); + font-size: 0.78rem; + font-weight: 800; + line-height: 1.2; + text-transform: uppercase; +} + +.empty-state { + display: grid; + gap: 8px; } .library-search { @@ -863,13 +1000,24 @@ textarea:focus { grid-template-columns: 1fr; } + .library-row, + .record-detail-grid { + grid-template-columns: 1fr; + } + .admin-access-row, .admin-user-main, - .panel-heading { + .panel-heading, + .record-heading { align-items: stretch; flex-direction: column; } + .record-actions, + .library-row-actions { + justify-content: flex-start; + } + .admin-badges { justify-content: flex-start; } diff --git a/public/medical.php b/public/medical.php index 33d6700..452e950 100644 --- a/public/medical.php +++ b/public/medical.php @@ -53,6 +53,12 @@ require __DIR__ . '/partials/site-header.php'; + +
+

No conditions found.

+

Try another medicine, symptom, lab, or tracking term.

+
+
@@ -60,19 +66,30 @@ require __DIR__ . '/partials/site-header.php';

Terminology

Plain-language medical terms

-
- -
-
-

- -
-

-

- -
- -
+ +
+

No terms found.

+

Try a broader medical word, lab name, symptom, or abbreviation.

+
+ +
+ + + +
+
@@ -80,28 +97,31 @@ require __DIR__ . '/partials/site-header.php';

Medication catalog

Medicines with chemical, brand, RxNorm, DailyMed, and PubChem links

-
- - - -
+ +
+

No medicines found.

+

Try a generic name, brand name, chemical name, or medication class.

+
+ +
+ + + +
+
diff --git a/public/medicine.php b/public/medicine.php new file mode 100644 index 0000000..e0eb6d6 --- /dev/null +++ b/public/medicine.php @@ -0,0 +1,125 @@ + +
+ +
+

Medicine catalog

+

Medicine not found.

+

The requested medicine link does not match a current catalog record.

+ Search medicines +
+ +
+
+
+

Medicine detail

+

+

+
+ +
+ +
+ + + ID +
+
+ +
+
+
+ Active Ingredient +

+
+
+ Chemical Name +

+
+
+ Common Brands +

+
+
+ Generic / Alternate Names +

+
+
+ Tracking Note +

+
+
+ Reference Links + +
+
+
+ + +
+
+

Research

+

Related NIH/NLM study links

+
+
+ + + +
+
+ + +
+ diff --git a/public/nutrition.php b/public/nutrition.php index 8d772a3..9b585d6 100644 --- a/public/nutrition.php +++ b/public/nutrition.php @@ -46,27 +46,37 @@ require __DIR__ . '/partials/site-header.php';
-
- -
-

-

-

Active ingredient:

-

Chemical name:

-

Common brands:

-

Generic/alternate names:

-

-

- +
+
+

Catalog directory

+

Medicine records

+
+ + +
+

No medicines found.

+

Try a generic name, brand name, chemical name, or medication class.

- + +
+ + + +
+
diff --git a/public/term.php b/public/term.php new file mode 100644 index 0000000..0be16d0 --- /dev/null +++ b/public/term.php @@ -0,0 +1,128 @@ + (string) $term['term'], 'limit' => 8]); + $relatedMedicines = $related['medicationCatalog']; + $relatedConditions = $related['medicalConditions']; + $relatedStudies = $related['researchStudies']; +} else { + http_response_code(404); +} + +require __DIR__ . '/partials/site-header.php'; +?> +
+ +
+

Medical terminology

+

Term not found.

+

The requested terminology link does not match a current database record.

+ Search terminology +
+ +
+
+
+

Medical term

+

+

+
+ +
+ +
+ + ID +
+
+ +
+
+
+ Clinical Context +

+
+
+ Source + +
+
+
+ + +
+
+

Connected records

+

Database matches for this term

+
+
+ +
+
+

+

+
+ +
+
+
+ Source +
+
+ + + + + + + + + +
+
+ + +
+