+
Dashboard
diff --git a/src/components/Home.jsx b/src/components/Home.jsx
new file mode 100644
index 00000000..cdc3e00a
--- /dev/null
+++ b/src/components/Home.jsx
@@ -0,0 +1,439 @@
+import {
+ Activity,
+ ArrowRight,
+ BellRing,
+ Brain,
+ BriefcaseMedical,
+ Check,
+ CheckCircle2,
+ Clock4,
+ FileText,
+ Heart,
+ Pill,
+ ShieldCheck
+} from 'lucide-react'
+import heroImage from '../assets/doctor-hero.jpg'
+import doctorPatientImage from '../assets/doctor-patient.jpg'
+import medicalProfessionalImage from '../assets/medical-professional.jpg'
+
+function Home({
+ user,
+ onGoToDashboard = () => {},
+ onLogout,
+ onLoginRequest = () => {},
+ isLoggedIn = Boolean(user)
+}) {
+ const isAuthenticated = Boolean(isLoggedIn && user)
+ const displayName = isAuthenticated
+ ? user?.fullName || user?.name || user?.username || 'User'
+ : 'Guest'
+ const displayRole = isAuthenticated && user?.role ? user.role.replace(/-/g, ' ') : null
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function SiteHeader({ displayName, displayRole, onGoToDashboard, onLogout, onLoginRequest, isAuthenticated }) {
+ return (
+
+
+
MedSync
+
+
+
+ Home
+
+
+ Services
+
+
+ Why MedSync
+
+
+
+
+ {isAuthenticated ? (
+ <>
+
+
Signed in as
+
{displayName}
+ {displayRole &&
{displayRole}
}
+
+
+
+ Lunch Dash Board
+
+
+ {onLogout && (
+
+ Logout
+
+ )}
+ >
+ ) : (
+
+ Log In
+
+ )}
+
+
+
+ )
+}
+
+function HeroSection({ onGoToDashboard, onLoginRequest, isAuthenticated }) {
+ return (
+
+
+
+
+
+
+ HIPAA-ready clinic management
+
+
+
+ Coordinate patient care with zero friction
+
+
+
+ MedSync keeps appointments, treatment notes, and billing workflows organised so your team can focus on people—not paperwork.
+
+
+
+
+ Lunch Dash Board
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
24/7
+
Digital triage & support
+
+
+
120+
+
Clinics stay in sync
+
+
+
98%
+
Patient satisfaction
+
+
+
+
+
+ )
+}
+
+function ServicesSection() {
+ const services = [
+ {
+ icon: Activity,
+ title: 'Urgent & Primary Care',
+ description:
+ 'Same-day triage and vitals logging synced to every patient chart within minutes.',
+ highlighted: false
+ },
+ {
+ icon: Heart,
+ title: 'Cardiology Program',
+ description:
+ 'Evidence-based treatment pathways with care plans, monitoring kits, and follow-up nudges.',
+ highlighted: true
+ },
+ {
+ icon: Pill,
+ title: 'Specialty Pharmacy',
+ description:
+ 'Integrated prescriptions and stock tracking that alert the team before supply dips.',
+ highlighted: false
+ },
+ {
+ icon: Brain,
+ title: 'Behavioral Health',
+ description:
+ 'Collaborative notes and consent workflows built for therapists and counsellors.',
+ highlighted: false
+ }
+ ]
+
+ return (
+
+
+
+ Our consulting specialists
+
+
+ Every module is crafted with clinical specialists to keep care plans, lab integrations, and billing logic aligned across your entire network.
+
+
+
+ {services.map(({ icon: Icon, title, description, highlighted }) => (
+
+
+
+
+
{title}
+
{description}
+
+ ))}
+
+
+
+ )
+}
+
+function WhyChooseSection() {
+ const benefits = [
+ 'Unified scheduling, billing, and medical records in one secure workspace.',
+ 'Role-based dashboards tailor information to front desk, clinicians, and finance teams.',
+ 'Real-time capacity insights so branches can balance patient load effortlessly.',
+ 'Automated follow-ups and reminders keep patients engaged between visits.'
+ ]
+
+ return (
+
+
+
+
+
+
+
+
+
+ Multi-location ready
+
+
+
+
+
+
Average wait time
+
12 minutes
+
+
+
+
+
+
Why teams choose MedSync
+
+
+ {benefits.map((benefit) => (
+
+
+
+
+ {benefit}
+
+ ))}
+
+
+
+
+
+ )
+}
+
+function QualityHealthSection() {
+ const features = [
+ {
+ icon:
,
+ title: 'Clinical checklists',
+ description: 'Structured visit notes that adapt to specialty protocols.'
+ },
+ {
+ icon:
,
+ title: 'Paperless onboarding',
+ description: 'Digitise intake forms with e-sign and insurance capture.'
+ },
+ {
+ icon:
,
+ title: 'Smart reminders',
+ description: 'Notify patients via SMS and email with one click.'
+ },
+ {
+ icon:
,
+ title: 'Outcome tracking',
+ description: 'Measure progress and satisfaction across every visit.'
+ }
+ ]
+
+ return (
+
+
+
+
+
+ The Future of Quality Health
+
+
+
+
+ Deliver proactive, personalised care from the first intake call to the final claim. MedSync connects every branch to one source of truth, so care teams can collaborate without searching endless spreadsheets.
+
+
+ Automated handoffs, configurable workflows, and real-time alerts give your clinicians the context they need before patients even walk through the door.
+
+
+
+
+ {features.map((feature) => (
+
+ {feature.description}
+
+ ))}
+
+
+
+
+
+
+
+
+
Care pathway
+
Recovery plan shared with patient
+
All departments notified and synced.
+
+
+
+
+
+ )
+}
+
+function Feature({ icon, title, children }) {
+ return (
+
+ )
+}
+
+function SiteFooter() {
+ return (
+
+ )
+}
+
+export default Home
diff --git a/src/components/Login.jsx b/src/components/Login.jsx
index f68e19fd..26e84307 100644
--- a/src/components/Login.jsx
+++ b/src/components/Login.jsx
@@ -1,7 +1,7 @@
import { useState } from 'react'
import { api, API_ENDPOINTS } from '../config/api'
-function Login({ onLogin }) {
+function Login({ onLogin, onBack }) {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
@@ -45,7 +45,16 @@ function Login({ onLogin }) {
}
return (
-
+
+ {onBack && (
+
+ ← Back to Home
+
+ )}
MedSync
diff --git a/src/components/Patients.jsx b/src/components/Patients.jsx
index 0a8bcb4d..d0df3b84 100644
--- a/src/components/Patients.jsx
+++ b/src/components/Patients.jsx
@@ -83,35 +83,35 @@ function Patients() {
return (
<>
-
-
+
+
Patients
setIsRegisterModalOpen(true)}
- className="px-6 py-3 border-2 border-crimson text-crimson text-xs font-semibold uppercase tracking-wider hover:bg-crimson hover:text-white transition-all"
+ className="px-6 py-3 border-2 border-crimson text-xs font-semibold uppercase tracking-wider text-crimson transition-all hover:bg-crimson hover:text-white"
>
Register New Patient
{/* Patient Search */}
-
-
-
Search
+
+
+ Search
setSearchTerm(e.target.value)}
- className="w-full px-2 py-2 border border-black text-xs focus:outline-2 focus:outline-black focus:outline-offset-2"
+ className="w-full rounded border border-black px-2 py-2 text-xs focus:outline-2 focus:outline-black focus:outline-offset-2"
/>
-
-
Branch
+
+ Branch
setBranchFilter(e.target.value)}
- className="w-full px-2 py-2 border border-black text-xs focus:outline-2 focus:outline-black focus:outline-offset-2"
+ className="w-full rounded border border-black px-2 py-2 text-xs focus:outline-2 focus:outline-black focus:outline-offset-2"
>
All Branches
{branches.map(branch => (
@@ -124,81 +124,85 @@ function Patients() {
{error && (
-
+
{error}
)}
{/* Patients Table */}
-
-
-
Patient Registry
-
- Export
-
-
-
-
-
-
- Patient ID
- Name
- Phone
- Email
- Last Visit
- Outstanding
- Actions
-
-
-
- {loading ? (
-
-
- Loading patients...
-
-
- ) : filteredPatients.length === 0 ? (
-
-
- No patients found
-
-
- ) : (
- filteredPatients.map((patient, index) => (
-
-
- {patient.id}
-
-
- {patient.first_name} {patient.last_name}
-
-
- {patient.phone}
-
-
- {patient.email || '-'}
-
-
- {patient.updated_at ? new Date(patient.updated_at).toLocaleDateString() : '-'}
-
- 0 ? 'text-crimson font-semibold' : ''} ${index !== filteredPatients.length - 1 ? 'border-b border-gray-light' : ''}`}>
- {outstandingBalances[patient.id] !== undefined
- ? `LKR ${outstandingBalances[patient.id].toFixed(2)}`
- : 'Loading...'}
-
-
- handleViewPatient(patient.id)}
- className="px-4 py-2 border-2 border-black bg-white text-black text-[10px] font-semibold uppercase tracking-wider hover:bg-black hover:text-white transition-all"
- >
- View
-
-
+
+
+
+
Patient Registry
+
+ Export
+
+
+
+
+
+
+
+ Patient ID
+ Name
+ Phone
+ Email
+ Last Visit
+ Outstanding
+ Actions
- ))
- )}
-
-
+
+
+ {loading ? (
+
+
+ Loading patients...
+
+
+ ) : filteredPatients.length === 0 ? (
+
+
+ No patients found
+
+
+ ) : (
+ filteredPatients.map((patient, index) => (
+
+
+ {patient.id}
+
+
+ {patient.first_name} {patient.last_name}
+
+
+ {patient.phone}
+
+
+ {patient.email || '-'}
+
+
+ {patient.updated_at ? new Date(patient.updated_at).toLocaleDateString() : '-'}
+
+ 0 ? 'text-crimson font-semibold' : ''} ${index !== filteredPatients.length - 1 ? 'border-b border-gray-light' : ''}`}>
+ {outstandingBalances[patient.id] !== undefined
+ ? `LKR ${outstandingBalances[patient.id].toFixed(2)}`
+ : 'Loading...'}
+
+
+ handleViewPatient(patient.id)}
+ className="px-4 py-2 text-[10px] font-semibold uppercase tracking-wider border-2 border-black bg-white text-black transition-all hover:bg-black hover:text-white"
+ >
+ View
+
+
+
+ ))
+ )}
+
+
+
+
diff --git a/src/components/QuickActions.jsx b/src/components/QuickActions.jsx
index 7c7a6c83..73a5ee82 100644
--- a/src/components/QuickActions.jsx
+++ b/src/components/QuickActions.jsx
@@ -33,15 +33,15 @@ function QuickActions({ onRefresh }) {
return (
<>
-
Quick Actions
-
+
Quick Actions
+
{actions.map((action, index) => (
-
+
{action.title}
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 407968a7..e5a0208c 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -67,21 +67,46 @@ const roleLabels = {
'patient': 'Patient'
}
-function Sidebar({ activeSection, onNavigate, userRole = 'front-desk', onLogout }) {
+function Sidebar({
+ activeSection,
+ onNavigate,
+ userRole = 'front-desk',
+ onLogout,
+ className = '',
+ onClose
+}) {
const navItems = roleNavigations[userRole] || roleNavigations['front-desk']
return (
-
+
{/* Sidebar Header */}
-
MedSync
-
+
+ onNavigate('home')}
+ className="text-left text-lg font-semibold text-black transition-colors hover:text-crimson"
+ >
+ MedSync
+
+ {onClose && (
+
+ ×
+
+ )}
+
+
{roleLabels[userRole]}
{/* Sidebar Navigation */}
-
+
{navItems.map((item) => (
onNavigate(item.id)}
+ onClick={() => onNavigate(item.id === 'dashboard' && activeSection === 'dashboard' ? 'home' : item.id)}
>
{item.label}
diff --git a/src/components/StatsGrid.jsx b/src/components/StatsGrid.jsx
index 5673dc50..3e7c6924 100644
--- a/src/components/StatsGrid.jsx
+++ b/src/components/StatsGrid.jsx
@@ -38,10 +38,10 @@ function StatsGrid() {
]
return (
-
+
{statsDisplay.map((stat, index) => (
-
-
+
+
{stat.label}
-
-
-
- Upcoming Appointments (Next 2 Hours)
-
-
- View All
-
-
-
-
-
-
- Time
-
-
- Patient
-
-
- Doctor
-
-
- Specialty
-
-
- Status
-
-
- Actions
-
-
-
-
- {loading ? (
-
-
- Loading appointments...
-
-
- ) : appointments.length === 0 ? (
-
-
- No upcoming appointments in the next 2 hours
-
-
- ) : (
- appointments.map((appointment, index) => (
-
-
- {formatTime(appointment.appointment_datetime)}
-
-
- {appointment.patient_first_name} {appointment.patient_last_name}
-
-
- Dr. {appointment.doctor_first_name} {appointment.doctor_last_name}
-
-
- {appointment.specialty || '-'}
-
-
-
- {appointment.status}
-
-
-
- handleViewAppointment(appointment.id)}
- className="px-4 py-2 border-2 border-black bg-white text-black text-[10px] font-semibold uppercase tracking-wider transition-all hover:bg-black hover:text-white"
- >
- View
-
-
+
+
+
+ Upcoming Appointments (Next 2 Hours)
+
+
+ View All
+
+
+
+
+
+
+
+ Time
+
+
+ Patient
+
+
+ Doctor
+
+
+ Specialty
+
+
+ Status
+
+
+ Actions
+
- ))
- )}
-
-
+
+
+ {loading ? (
+
+
+ Loading appointments...
+
+
+ ) : appointments.length === 0 ? (
+
+
+ No upcoming appointments in the next 2 hours
+
+
+ ) : (
+ appointments.map((appointment, index) => (
+
+
+ {formatTime(appointment.appointment_datetime)}
+
+
+ {appointment.patient_first_name} {appointment.patient_last_name}
+
+
+ Dr. {appointment.doctor_first_name} {appointment.doctor_last_name}
+
+
+ {appointment.specialty || '-'}
+
+
+
+ {appointment.status}
+
+
+
+ handleViewAppointment(appointment.id)}
+ className="px-4 py-2 text-[10px] font-semibold uppercase tracking-wider border-2 border-black bg-white text-black transition-all hover:bg-black hover:text-white"
+ >
+ View
+
+
+
+ ))
+ )}
+
+
+
{/* Appointment Detail Modal */}
diff --git a/src/index.css b/src/index.css
index 37986a15..38dc6de4 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,15 +2,84 @@
@tailwind components;
@tailwind utilities;
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
-}
+@layer base {
+ :root {
+ --background: 227 62% 97%;
+ --foreground: 228 35% 20%;
+
+ --card: 227 65% 99%;
+ --card-foreground: 228 35% 20%;
+
+ --popover: 227 65% 99%;
+ --popover-foreground: 228 35% 20%;
+
+ --primary: 227 68% 54%;
+ --primary-foreground: 0 0% 100%;
+
+ --secondary: 205 72% 55%;
+ --secondary-foreground: 0 0% 100%;
+
+ --muted: 228 60% 94%;
+ --muted-foreground: 227 27% 44%;
+
+ --accent: 269 68% 92%;
+ --accent-foreground: 266 53% 44%;
+
+ --destructive: 350 70% 61%;
+ --destructive-foreground: 0 0% 100%;
+
+ --border: 226 40% 88%;
+ --input: 226 40% 88%;
+ --ring: 227 68% 54%;
+
+ --radius: 0.75rem;
+
+ --hero-gradient-start: 227 68% 97%;
+ --hero-gradient-end: 227 85% 95%;
+ --stats-bg: 227 63% 50%;
+ }
+
+ .dark {
+ --background: 229 35% 12%;
+ --foreground: 220 40% 96%;
+
+ --card: 229 28% 15%;
+ --card-foreground: 220 40% 96%;
+
+ --popover: 229 28% 15%;
+ --popover-foreground: 220 40% 96%;
+
+ --primary: 227 68% 61%;
+ --primary-foreground: 0 0% 100%;
+
+ --secondary: 204 72% 58%;
+ --secondary-foreground: 0 0% 100%;
+
+ --muted: 229 26% 26%;
+ --muted-foreground: 227 20% 72%;
+
+ --accent: 268 32% 32%;
+ --accent-foreground: 268 70% 86%;
+
+ --destructive: 350 74% 58%;
+ --destructive-foreground: 0 0% 100%;
+
+ --border: 228 26% 28%;
+ --input: 228 26% 28%;
+ --ring: 227 68% 61%;
+ }
+
+ * {
+ @apply border-border;
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ }
-body {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
- background: #FFFFFF;
- color: #0A0A0A;
- line-height: 1.5;
+ body {
+ @apply bg-background text-foreground;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ line-height: 1.5;
+ -webkit-font-smoothing: antialiased;
+ }
}
diff --git a/tailwind.config.js b/tailwind.config.js
index c3bbb56e..8e4ae764 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -5,14 +5,73 @@ export default {
"./src/**/*.{js,ts,jsx,tsx}",
],
theme: {
+ container: {
+ center: true,
+ padding: "2rem",
+ screens: {
+ "2xl": "1400px",
+ },
+ },
extend: {
colors: {
- crimson: '#DC143C',
- purple: '#9333EA',
- 'gray-dark': '#404040',
- 'gray-mid': '#808080',
- 'gray-light': '#D4D4D4',
- 'gray-bg': '#F5F5F5',
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ crimson: '#E55670',
+ purple: '#6366F1',
+ 'gray-dark': '#1F2937',
+ 'gray-mid': '#475569',
+ 'gray-light': '#D9DEFA',
+ 'gray-bg': '#EEF2FF',
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "fade-in": {
+ "0%": { opacity: "0", transform: "translateY(10px)" },
+ "100%": { opacity: "1", transform: "translateY(0)" },
+ },
+ "slide-up": {
+ "0%": { opacity: "0", transform: "translateY(20px)" },
+ "100%": { opacity: "1", transform: "translateY(0)" },
+ },
+ },
+ animation: {
+ "fade-in": "fade-in 0.5s ease-out",
+ "slide-up": "slide-up 0.6s ease-out",
},
},
},
@@ -23,17 +82,17 @@ export default {
themes: [
{
medsync: {
- "primary": "#0A0A0A",
- "secondary": "#DC143C",
- "accent": "#9333EA",
- "neutral": "#404040",
- "base-100": "#FFFFFF",
- "base-200": "#F5F5F5",
- "base-300": "#D4D4D4",
- "info": "#808080",
- "success": "#0A0A0A",
- "warning": "#9333EA",
- "error": "#DC143C",
+ "primary": "#4C5BD6",
+ "secondary": "#39A7DB",
+ "accent": "#5E4AB2",
+ "neutral": "#1F2937",
+ "base-100": "#F5F7FF",
+ "base-200": "#EBEEFF",
+ "base-300": "#D9DEFA",
+ "info": "#39A7DB",
+ "success": "#3BB273",
+ "warning": "#F59E0B",
+ "error": "#E55670",
},
},
],