Skip to content

Commit 6b7bebb

Browse files
committed
FDAiFrameworkMinimal, OutcomeSearchAutocomplete, PredictorSearchAutocomplete, TreatmentDatabasePage, FeatureBox, and home-page.tsx for feature enhancements. Updated SearchAutocomplete with a brutalist styling option, and refined CostBenefitAnalysis to utilize GlobalVariable for treatment selection.
Took 2 minutes
1 parent e4184b2 commit 6b7bebb

23 files changed

+2418
-248
lines changed

app/dfda/components/AdvancedTrialSearch.tsx

Lines changed: 510 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'use client'
2+
3+
import React, { useState } from 'react'
4+
import { Search, ArrowRight } from 'lucide-react'
5+
import { useRouter } from 'next/navigation'
6+
import { searchConditions } from '@/lib/clinicaltables'
7+
import { motion, AnimatePresence } from 'framer-motion'
8+
9+
export default function ClinicalTrialSearch() {
10+
const router = useRouter()
11+
const [condition, setCondition] = useState('')
12+
const [suggestions, setSuggestions] = useState<string[]>([])
13+
const [loading, setLoading] = useState(false)
14+
15+
const handleSearch = async (e: React.FormEvent) => {
16+
e.preventDefault()
17+
if (condition.trim()) {
18+
router.push(`/trials/${encodeURIComponent(condition.trim())}`)
19+
}
20+
}
21+
22+
const handleInputChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
23+
const value = e.target.value
24+
setCondition(value)
25+
26+
if (value.trim().length > 2) {
27+
setLoading(true)
28+
try {
29+
const results = await searchConditions(value)
30+
setSuggestions(results.slice(0, 5))
31+
} catch (error) {
32+
console.error('Error fetching suggestions:', error)
33+
}
34+
setLoading(false)
35+
} else {
36+
setSuggestions([])
37+
}
38+
}
39+
40+
const handleSuggestionClick = (suggestion: string) => {
41+
setCondition(suggestion)
42+
setSuggestions([])
43+
router.push(`/trials/${encodeURIComponent(suggestion)}`)
44+
}
45+
46+
return (
47+
<div className="w-full">
48+
<form onSubmit={handleSearch} className="relative mb-6">
49+
<div className="flex flex-col gap-4 md:flex-row">
50+
<div className="relative flex-grow">
51+
<Search className="absolute left-4 top-1/2 h-5 w-5 -translate-y-1/2 transform text-gray-500" />
52+
<input
53+
type="text"
54+
placeholder="Enter your condition (e.g., Type 2 Diabetes)"
55+
value={condition}
56+
onChange={handleInputChange}
57+
className="w-full rounded-xl border-4 border-black bg-white px-12 py-4 text-lg font-bold shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] outline-none transition-all focus:translate-x-1 focus:translate-y-1 focus:shadow-none"
58+
/>
59+
</div>
60+
<button
61+
type="submit"
62+
className="group flex items-center gap-2 rounded-xl border-4 border-black bg-white px-6 py-4 font-bold shadow-[4px_4px_0px_0px_rgba(0,0,0,1)] transition-all hover:translate-x-1 hover:translate-y-1 hover:shadow-none"
63+
>
64+
Find Trials
65+
<ArrowRight className="transition-transform group-hover:translate-x-1" />
66+
</button>
67+
</div>
68+
69+
{/* Suggestions dropdown */}
70+
{suggestions.length > 0 && (
71+
<div className="absolute z-10 mt-2 w-full rounded-xl border-4 border-black bg-white shadow-[4px_4px_0px_0px_rgba(0,0,0,1)]">
72+
{suggestions.map((suggestion, index) => (
73+
<button
74+
key={index}
75+
onClick={() => handleSuggestionClick(suggestion)}
76+
className="w-full border-b border-gray-200 px-4 py-3 text-left font-bold hover:bg-gray-100 last:border-none"
77+
type="button"
78+
>
79+
{suggestion}
80+
</button>
81+
))}
82+
</div>
83+
)}
84+
</form>
85+
</div>
86+
)
87+
}

app/dfda/components/CostBenefitAnalysis.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/com
44
import { Stethoscope, Pill, BarChart } from "lucide-react"
55
import ConditionSearchAutocomplete from './ConditionSearchAutocomplete'
66
import TreatmentSearchAutocomplete from './TreatmentSearchAutocomplete'
7+
import { GlobalVariable } from '@/types/models/GlobalVariable'
78

89
export default function CostBenefitAnalysis() {
910
const [condition, setCondition] = useState('')
1011
const [treatment, setTreatment] = useState('')
1112

13+
const handleTreatmentSelect = (treatmentVariable: GlobalVariable) => {
14+
setTreatment(treatmentVariable.name)
15+
}
16+
1217
const handleAnalyze = (e: React.FormEvent) => {
1318
e.preventDefault()
1419
console.log('Analyzing:', condition, treatment)
@@ -36,8 +41,7 @@ export default function CostBenefitAnalysis() {
3641
<div className="flex items-center space-x-2">
3742
<Pill className="text-gray-500" />
3843
<TreatmentSearchAutocomplete
39-
onTreatmentSelect={setTreatment}
40-
placeholder="Enter treatment"
44+
onVariableSelect={handleTreatmentSelect}
4145
/>
4246
</div>
4347
<div className="flex justify-center">
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use client'
2+
3+
import React, { useState } from 'react'
4+
import { motion, AnimatePresence } from 'framer-motion'
5+
import { clinicalTrialCostData, TOTAL_CURRENT_COST, TOTAL_NEW_COST } from '@/app/dfda/components/cost-savings-data'
6+
import type { CostItem } from '@/app/dfda/cost-savings'
7+
import { ChevronDown, ChevronUp } from 'lucide-react'
8+
9+
export default function CostSavingsTable() {
10+
const [expandedItem, setExpandedItem] = useState<string | null>(null)
11+
12+
const formatCurrency = (amount: number) => {
13+
return new Intl.NumberFormat('en-US', {
14+
style: 'currency',
15+
currency: 'USD',
16+
maximumFractionDigits: 0
17+
}).format(amount)
18+
}
19+
20+
const calculateSavings = (current: number, new_: number) => {
21+
return ((current - new_) / current * 100).toFixed(1)
22+
}
23+
24+
const DetailPanel = ({ item }: { item: CostItem }) => (
25+
<motion.div
26+
initial={{ height: 0, opacity: 0 }}
27+
animate={{ height: 'auto', opacity: 1 }}
28+
exit={{ height: 0, opacity: 0 }}
29+
transition={{ duration: 0.3 }}
30+
className="overflow-hidden bg-gray-50 px-4 py-6"
31+
>
32+
<div className="grid gap-6 md:grid-cols-3">
33+
<div className="rounded-xl border-2 border-black bg-yellow-100 p-4">
34+
<h4 className="mb-2 font-black">Current Process 📋</h4>
35+
<p>{item.currentExplanation}</p>
36+
<p className="mt-2 font-mono font-bold text-gray-700">
37+
Cost: {formatCurrency(item.currentCost)}
38+
</p>
39+
</div>
40+
41+
<div className="rounded-xl border-2 border-black bg-green-100 p-4">
42+
<h4 className="mb-2 font-black">Cost Reduction Strategy 📉</h4>
43+
<p>{item.reductionExplanation}</p>
44+
<p className="mt-2 font-mono font-bold text-gray-700">
45+
New Cost: {formatCurrency(item.newCost)}
46+
</p>
47+
</div>
48+
49+
<div className="rounded-xl border-2 border-black bg-blue-100 p-4">
50+
<h4 className="mb-2 font-black">Remaining Expenses 💡</h4>
51+
<p>{item.remainingExplanation}</p>
52+
<p className="mt-2 font-mono font-bold text-gray-700">
53+
Savings: {calculateSavings(item.currentCost, item.newCost)}%
54+
</p>
55+
</div>
56+
</div>
57+
</motion.div>
58+
)
59+
60+
return (
61+
<div className="relative overflow-hidden rounded-xl border-4 border-black bg-white p-6 shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
62+
<motion.h2
63+
className="mb-6 text-2xl md:text-4xl font-black uppercase"
64+
initial={{ opacity: 0, y: -20 }}
65+
animate={{ opacity: 1, y: 0 }}
66+
>
67+
Clinical Trial Cost Savings 💰
68+
</motion.h2>
69+
70+
<div className="-mx-6 sm:mx-0">
71+
<div className="min-w-full inline-block align-middle">
72+
<div className="overflow-x-auto">
73+
<table className="min-w-full border-collapse">
74+
<thead>
75+
<tr className="border-b-4 border-black bg-yellow-300">
76+
<th className="p-2 sm:p-4 text-left font-black">Cost Item</th>
77+
<th className="p-2 sm:p-4 text-right font-black hidden sm:table-cell">Current Cost</th>
78+
<th className="p-2 sm:p-4 text-right font-black hidden sm:table-cell">New Cost</th>
79+
<th className="p-2 sm:p-4 text-right font-black">Savings</th>
80+
</tr>
81+
</thead>
82+
<tbody>
83+
{clinicalTrialCostData.map((item, index) => (
84+
<React.Fragment key={item.name}>
85+
<motion.tr
86+
className={`cursor-pointer border-b-2 border-black hover:bg-gray-50 ${
87+
expandedItem === item.name ? 'bg-gray-50' : ''
88+
}`}
89+
initial={{ opacity: 0, x: -20 }}
90+
animate={{ opacity: 1, x: 0 }}
91+
transition={{ delay: index * 0.1 }}
92+
onClick={() => setExpandedItem(expandedItem === item.name ? null : item.name)}
93+
>
94+
<td className="p-2 sm:p-4 font-bold">
95+
<div className="flex items-center gap-2">
96+
{item.emoji} {item.name}
97+
{expandedItem === item.name ? (
98+
<ChevronUp className="h-4 w-4" />
99+
) : (
100+
<ChevronDown className="h-4 w-4" />
101+
)}
102+
</div>
103+
<div className="mt-1 text-sm text-gray-600 sm:hidden">
104+
{formatCurrency(item.currentCost)}{formatCurrency(item.newCost)}
105+
</div>
106+
</td>
107+
<td className="p-2 sm:p-4 text-right font-mono hidden sm:table-cell">{formatCurrency(item.currentCost)}</td>
108+
<td className="p-2 sm:p-4 text-right font-mono hidden sm:table-cell">{formatCurrency(item.newCost)}</td>
109+
<td className="p-2 sm:p-4 text-right">
110+
<span className="rounded-full bg-green-400 px-2 sm:px-3 py-1 font-bold text-sm sm:text-base">
111+
{calculateSavings(item.currentCost, item.newCost)}%
112+
</span>
113+
</td>
114+
</motion.tr>
115+
<tr>
116+
<td colSpan={4} className="p-0">
117+
<AnimatePresence>
118+
{expandedItem === item.name && <DetailPanel item={item} />}
119+
</AnimatePresence>
120+
</td>
121+
</tr>
122+
</React.Fragment>
123+
))}
124+
<motion.tr
125+
className="border-t-4 border-black bg-green-300 font-black"
126+
initial={{ opacity: 0, y: 20 }}
127+
animate={{ opacity: 1, y: 0 }}
128+
transition={{ delay: clinicalTrialCostData.length * 0.1 }}
129+
>
130+
<td className="p-2 sm:p-4">
131+
TOTAL SAVINGS 🎉
132+
<div className="text-sm mt-1 sm:hidden">
133+
{formatCurrency(TOTAL_CURRENT_COST)}{formatCurrency(TOTAL_NEW_COST)}
134+
</div>
135+
</td>
136+
<td className="p-2 sm:p-4 text-right hidden sm:table-cell">{formatCurrency(TOTAL_CURRENT_COST)}</td>
137+
<td className="p-2 sm:p-4 text-right hidden sm:table-cell">{formatCurrency(TOTAL_NEW_COST)}</td>
138+
<td className="p-2 sm:p-4 text-right">
139+
<span className="rounded-full bg-black px-2 sm:px-3 py-1 text-white text-sm sm:text-base">
140+
95.7%
141+
</span>
142+
</td>
143+
</motion.tr>
144+
</tbody>
145+
</table>
146+
</div>
147+
</div>
148+
</div>
149+
150+
<div className="mt-6 rounded-xl border-2 border-black bg-blue-100 p-4">
151+
<h3 className="mb-2 font-black">💡 Key Benefits</h3>
152+
<ul className="list-inside list-disc space-y-2">
153+
<li>Automated processes reduce manual labor costs</li>
154+
<li>AI-driven analysis improves efficiency and accuracy</li>
155+
<li>Decentralized approach eliminates site-related expenses</li>
156+
<li>Blockchain technology ensures data integrity</li>
157+
</ul>
158+
</div>
159+
</div>
160+
)
161+
}

app/dfda/components/DFDAFooter.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use client'
2+
3+
export default function DFDAFooter() {
4+
return (
5+
<footer className="mt-12 rounded-xl border-4 border-black bg-white p-4 text-center font-bold shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
6+
<p>&copy; 2023 dFDA - Empowering patients, revolutionizing healthcare 🚀</p>
7+
</footer>
8+
)
9+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client"
2+
3+
import React from "react"
4+
import Link from "next/link"
5+
import { NavItem } from "@/types"
6+
import { User } from "next-auth"
7+
8+
import { dfdaAvatarNav, generalDashboardTopNav } from "@/config/links"
9+
import { UserNavDisplay } from "@/components/user/user-nav-display"
10+
import { DfdaLogoNavMenu } from "./dfda-logo-nav"
11+
12+
interface NavbarProps extends React.HTMLAttributes<HTMLDivElement> {
13+
user: Pick<User, "name" | "image" | "email">
14+
logoNavItems?: NavItem[]
15+
topNavItems?: NavItem[]
16+
avatarNavItems?: NavItem[]
17+
}
18+
19+
export default function DfdaTopNavbar({
20+
user,
21+
logoNavItems,
22+
topNavItems,
23+
avatarNavItems,
24+
}: NavbarProps) {
25+
if (!topNavItems) {
26+
topNavItems = generalDashboardTopNav.data
27+
}
28+
if(!avatarNavItems) {
29+
avatarNavItems = dfdaAvatarNav.data
30+
}
31+
return (
32+
<header className="select-none">
33+
<nav className="mx-auto flex items-center justify-between px-4 md:px-8 lg:max-w-7xl">
34+
<div>
35+
<div className="flex items-center justify-between py-3 md:block md:py-5">
36+
<DfdaLogoNavMenu navItems={logoNavItems}></DfdaLogoNavMenu>
37+
</div>
38+
</div>
39+
<div className="hidden md:block">
40+
<div
41+
className="absolute left-0 right-0 z-10 m-auto justify-self-center rounded-md border p-4 md:static md:mt-0 md:border-none md:p-0"
42+
style={{ width: "100%", maxWidth: "20rem" }}
43+
>
44+
<ul className="flex flex-col items-center space-y-4 opacity-60 md:flex-row md:space-x-6 md:space-y-0">
45+
{topNavItems.map((item, index) => {
46+
return (
47+
item.href && (
48+
<Link
49+
key={index}
50+
href={item.disabled ? "/" : item.href}
51+
className="hover:underline"
52+
>
53+
{item.title}
54+
</Link>
55+
)
56+
)
57+
})}
58+
</ul>
59+
</div>
60+
</div>
61+
<UserNavDisplay
62+
user={{
63+
name: user?.name,
64+
image: user?.image,
65+
email: user?.email,
66+
}}
67+
avatarNavItems={avatarNavItems}
68+
/>
69+
</nav>
70+
</header>
71+
)
72+
}

0 commit comments

Comments
 (0)