Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8cc6f19
adds service files for quicklab
aditya241104 Dec 16, 2025
a0f0ffd
modifis login and register page for lab admin and lab staff
aditya241104 Dec 16, 2025
636d826
corrects the navbar login register functionality
aditya241104 Dec 17, 2025
69f5d4a
fixes checklabexists method in labadmincontroller
aditya241104 Dec 17, 2025
7f981c1
feat: add lab staff setup and prevent guards for profile and lab assi…
aditya241104 Dec 17, 2025
7696e5b
feat: add lab staff pages (profile, waiting assignment, dashboard)
aditya241104 Dec 17, 2025
d14de41
feat: integrate lab staff routes with guards into quicklab routing
aditya241104 Dec 17, 2025
1643286
feat: update dashboard routing for lab_staff role
aditya241104 Dec 17, 2025
a935e0b
fix: prevent infinite redirect loops in lab admin guards with checked…
aditya241104 Dec 17, 2025
6429ccf
feat: add lab admin dashboard page with stats and quick actions
aditya241104 Dec 17, 2025
19c9444
feat(quicklab): add LabAdminManageStaff page with add-panel for unass…
aditya241104 Dec 17, 2025
5937e3b
feat(routes): add /quick-lab/staff route for lab_admin with guards an…
aditya241104 Dec 17, 2025
524f65d
feat(dashboard): add Manage Staff navigation and placeholders for tes…
aditya241104 Dec 17, 2025
2a83022
feat(navbar): add lab_admin-only Staff shortcut in mobile navbar
aditya241104 Dec 17, 2025
949545f
fix(navbar): switch desktop navbar to sticky to prevent content overlap
aditya241104 Dec 17, 2025
dfd0d05
fix(lab-admin): handle staff removal when not assigned and return cle…
aditya241104 Dec 17, 2025
8bd065a
feat(prescription): add labId field to tests for lab assignment
aditya241104 Dec 20, 2025
1bf2bd2
feat(api): add getNearbyLabs endpoint to fetch labs in same city
aditya241104 Dec 20, 2025
e97ec1e
feat(service): add getNearbyLabs API call in prescription service
aditya241104 Dec 20, 2025
4763696
feat(prescription): add lab selector for tests with nearby lab filter…
aditya241104 Dec 20, 2025
cc5ae30
feat(quicklab): add geolocation service with browser API and IP fallback
aditya241104 Dec 20, 2025
565dbc5
feat(quicklab): add lab service with search, details, and booking APIs
aditya241104 Dec 20, 2025
7a23b54
feat(quicklab): add public API endpoints for lab search and details
aditya241104 Dec 20, 2025
ce3609a
feat(quicklab): add lab search results page with filters and dynamic …
aditya241104 Dec 20, 2025
2bbc33e
feat(quicklab): add lab details page with test selection and booking
aditya241104 Dec 20, 2025
0d30765
feat(quicklab): add search suggestions with autocomplete to navbar
aditya241104 Dec 20, 2025
1c63098
feat(quicklab): add routes for search and lab details pages
aditya241104 Dec 20, 2025
2a63c0b
fix(backend): convert sampleType to lowercase and remove dynamic import
aditya241104 Dec 21, 2025
a1f5734
fix(backend): correct import paths for Lab models
aditya241104 Dec 21, 2025
0cb83e6
feat(quicklab): add structured address collection for lab appointments
aditya241104 Dec 21, 2025
6c48cfd
feat(quicklab): add lab admin appointments management page
aditya241104 Dec 21, 2025
8b611ca
feat(quicklab): add lab admin test management page
aditya241104 Dec 21, 2025
95ab1da
feat(quicklab): add routes for appointments and test management
aditya241104 Dec 21, 2025
ddf859e
feat(quicklab): add lab admin navigation buttons to desktop navbar
aditya241104 Dec 21, 2025
c19339b
feat(quicklab): add lab admin navigation to mobile bottom navbar
aditya241104 Dec 21, 2025
d7c4280
feat(quicklab): add role-based redirects on homepage
aditya241104 Dec 21, 2025
69f82b1
quicklab: add lab info update endpoint
aditya241104 Dec 22, 2025
444fe65
quicklab: add lab settings page and nav
aditya241104 Dec 22, 2025
5a1aadf
quicklab: enhance lab admin dashboard
aditya241104 Dec 22, 2025
5c46be5
adds appointment page for the lab staff
aditya241104 Dec 28, 2025
fac8e95
Add patient lab appointments experience
aditya241104 Dec 28, 2025
55be654
QuickClinic nav: add compact Explore dropdown linking to Quick Med an…
aditya241104 Dec 28, 2025
911568f
Add Explore nav: QuickLab & QuickMed cross-links for patients/public …
aditya241104 Dec 28, 2025
4e1c0af
adds quicklab option in template
aditya241104 Dec 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- [ ] QuickClinic
- [ ] QuickMed
- [ ] QuickLab

## Change Type

Expand Down
3 changes: 3 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ function AppInner() {
return <PatientRoutes />;
case 'doctor':
return <DoctorRoutes />;
case 'lab_admin':
case 'lab_staff':
return <QuickLabRoutes />;
default:
// Fallback for unknown roles
navigate('/', { replace: true });
Expand Down
45 changes: 45 additions & 0 deletions client/src/components/Patient/PatientDesktopNavbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,23 @@ import { useNavigate } from 'react-router-dom';
import { useAuth } from '../../context/authContext';

import SearchBar from './SearchBar';
import { useState, useRef, useEffect } from 'react';

const PatientDesktopNavbar = () => {
const navigate = useNavigate();
const { logout } = useAuth();
const [isExploreOpen, setIsExploreOpen] = useState(false);
const exploreRef = useRef(null);

useEffect(() => {
const onDocClick = (e) => {
if (exploreRef.current && !exploreRef.current.contains(e.target)) {
setIsExploreOpen(false);
}
};
document.addEventListener('mousedown', onDocClick);
return () => document.removeEventListener('mousedown', onDocClick);
}, []);

const handleLogout = async () => {
try {
Expand Down Expand Up @@ -72,6 +85,38 @@ const PatientDesktopNavbar = () => {
<span>Profile</span>
</button>

{/* Explore Dropdown */}
<div className="relative" ref={exploreRef}>
<button
onClick={() => setIsExploreOpen((v) => !v)}
className="flex items-center text-gray-700 hover:text-blue-600 transition-colors px-3 py-2 rounded-md hover:bg-gray-50"
>
<span>Explore</span>
</button>
{isExploreOpen && (
<div className="absolute right-0 mt-2 w-40 bg-white border border-gray-200 rounded-lg shadow-lg py-2">
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-med');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Med
</button>
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-lab');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Lab
</button>
</div>
)}
</div>

{/* Notification Bell Icon */}
<button
onClick={() => navigate('/patient/notifications')}
Expand Down
32 changes: 31 additions & 1 deletion client/src/components/Patient/PatientMobileNavigation.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const PatientMobileNavigation = () => {
const navigate = useNavigate();
const location = useLocation();
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [isExploreOpen, setIsExploreOpen] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const { logout } = useAuth();

Expand Down Expand Up @@ -59,7 +60,7 @@ const PatientMobileNavigation = () => {
{/* Rest of the component remains the same */}
{/* Mobile Bottom Navigation */}
<nav className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
<div className="flex justify-around items-center h-16">
<div className="flex justify-around items-center h-16 relative">
{bottomNavItems.map((item) => {
const Icon = item.icon;
const isActive = isActivePath(item.path);
Expand All @@ -76,6 +77,35 @@ const PatientMobileNavigation = () => {
</button>
);
})}
{/* Explore toggle */}
<button
onClick={() => setIsExploreOpen((v) => !v)}
className="flex flex-col items-center justify-center flex-1 h-full text-gray-600"
>
<span className="text-xs mt-1">Explore</span>
</button>
{isExploreOpen && (
<div className="absolute bottom-16 right-4 bg-white border border-gray-200 rounded-lg shadow-lg py-2 w-40">
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-med');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Med
</button>
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-lab');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Lab
</button>
</div>
)}
</div>
</nav>
{/* Rest of the hamburger menu and other components */}
Expand Down
12 changes: 5 additions & 7 deletions client/src/components/auth/AuthButton.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Loader2 } from 'lucide-react';

import Loading from '../ui/Loading';

export const AuthButton = ({
children,
isLoading = false,
Expand All @@ -12,12 +10,11 @@ export const AuthButton = ({
variant = 'primary',
}) => {
const baseClasses =
'w-full flex justify-center items-center gap-2 px-8 py-4 border border-transparent rounded-xl text-lg font-semibold transition-all duration-200 focus:outline-none focus:ring-4 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transform hover:scale-[1.02] active:scale-[0.98]';
'w-full flex justify-center items-center gap-2 px-4 py-3 rounded-lg font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';

const variantClasses = {
primary:
'text-white bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-700 hover:to-blue-800 focus:ring-blue-500 shadow-lg hover:shadow-xl',
secondary: 'text-blue-700 bg-blue-50 border-blue-200 hover:bg-blue-100 focus:ring-blue-500',
primary: 'text-white bg-blue-600 hover:bg-blue-700 focus:ring-blue-500',
secondary: 'text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:ring-gray-500',
};

const isDisabled = disabled || isLoading;
Expand All @@ -31,7 +28,8 @@ export const AuthButton = ({
>
{isLoading ? (
<>
<Loading />
<Loader2 className="w-5 h-5 animate-spin" />
<span>Please wait...</span>
</>
) : (
children
Expand Down
32 changes: 25 additions & 7 deletions client/src/components/auth/ErrorMessage.jsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { AlertCircle } from 'lucide-react';
import { AlertCircle, X } from 'lucide-react';
import { useState } from 'react';

export const ErrorMessage = ({ error, className = '' }) => {
if (!error) return null;
const [isDismissed, setIsDismissed] = useState(false);

if (!error || isDismissed) return null;

return (
<div
className={`bg-red-50 border border-red-200 rounded-xl p-4 flex items-start gap-3 ${className}`}
>
<AlertCircle className="h-5 w-5 text-red-500 mt-0.5 flex-shrink-0" />
<p className="text-sm text-red-800 font-medium">{error}</p>
<div className={`animate-slideDown ${className}`}>
<div className="relative p-4 rounded-xl bg-red-50 dark:bg-red-900/20 border-2 border-red-200 dark:border-red-800">
<div className="flex items-start gap-3">
<div className="flex-shrink-0">
<div className="w-8 h-8 rounded-lg bg-red-100 dark:bg-red-900/40 flex items-center justify-center">
<AlertCircle className="w-5 h-5 text-red-600 dark:text-red-400" />
</div>
</div>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium text-red-800 dark:text-red-200">{error}</p>
</div>
<button
onClick={() => setIsDismissed(true)}
className="flex-shrink-0 text-red-400 hover:text-red-600 dark:hover:text-red-300 transition-colors focus:outline-none"
>
<X className="w-5 h-5" />
</button>
</div>
</div>
</div>
);
};
Expand Down
34 changes: 17 additions & 17 deletions client/src/components/auth/PasswordInput.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Eye, EyeOff, Lock } from 'lucide-react';
import { Eye, EyeOff } from 'lucide-react';

export const PasswordInput = ({
id,
Expand All @@ -13,38 +13,38 @@ export const PasswordInput = ({
error = null,
}) => {
return (
<div className="space-y-2">
{label && (
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
{label} {required && <span className="text-red-500">*</span>}
</label>
)}
<div>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-4 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
id={id}
name={name}
type={showPassword ? 'text' : 'password'}
autoComplete={name === 'password' ? 'current-password' : 'new-password'}
required={required}
value={value}
onChange={onChange}
className={`
block w-full px-4 py-3 pr-11 border rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-transparent
transition-colors
${
error
? 'border-red-300 bg-red-50 dark:bg-red-900/10'
: 'border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-900'
}
text-gray-900 dark:text-white placeholder:text-gray-400
`}
placeholder={placeholder}
required={required}
className={`w-full pl-12 pr-12 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200 ${
error ? 'border-red-500 focus:ring-red-500' : ''
}`}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute inset-y-0 right-0 pr-4 flex items-center text-gray-400 hover:text-gray-600 transition-colors"
aria-label={showPassword ? 'Hide password' : 'Show password'}
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
{error && <p className="mt-2 text-sm text-red-600">{error}</p>}
{error && <p className="text-sm text-red-600 dark:text-red-400 mt-1">{error}</p>}
</div>
);
};
Expand Down
44 changes: 44 additions & 0 deletions client/src/components/public/DesktopNavbar.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { FiLogIn, FiMapPin, FiUser } from 'react-icons/fi';
import { useNavigate } from 'react-router-dom';
import { useState, useRef, useEffect } from 'react';
import SearchBar from './SearchBar';

const Navbar = () => {
const navigate = useNavigate();
const [isExploreOpen, setIsExploreOpen] = useState(false);
const exploreRef = useRef(null);

useEffect(() => {
const onDocClick = (e) => {
if (exploreRef.current && !exploreRef.current.contains(e.target)) {
setIsExploreOpen(false);
}
};
document.addEventListener('mousedown', onDocClick);
return () => document.removeEventListener('mousedown', onDocClick);
}, []);

return (
<nav className="bg-white shadow-md sticky top-0 z-50 hidden md:block">
Expand Down Expand Up @@ -37,6 +50,37 @@ const Navbar = () => {
<FiUser className="mr-2" />
<span className="whitespace-nowrap">Doctors</span>
</button>
{/* Explore Dropdown */}
<div className="relative" ref={exploreRef}>
<button
onClick={() => setIsExploreOpen((v) => !v)}
className="flex items-center text-gray-700 hover:text-blue-600 transition-colors px-3 py-2 rounded-md hover:bg-gray-50"
>
<span className="whitespace-nowrap">Explore</span>
</button>
{isExploreOpen && (
<div className="absolute right-0 mt-2 w-40 bg-white border border-gray-200 rounded-lg shadow-lg py-2">
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-med');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Med
</button>
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-lab');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Lab
</button>
</div>
)}
</div>
<button
onClick={() => navigate('/login')}
className="bg-gradient-to-r from-blue-600 to-blue-700 text-white px-4 py-2 rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all flex items-center shadow-sm"
Expand Down
33 changes: 32 additions & 1 deletion client/src/components/public/MobileBottomBar.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { FiLogIn, FiMapPin, FiSearch, FiUser } from 'react-icons/fi';
import { useNavigate } from 'react-router-dom';
import { useState } from 'react';

const MobileNavigation = () => {
const navigate = useNavigate();
const [isExploreOpen, setIsExploreOpen] = useState(false);
return (
<div className="md:hidden fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 z-50">
{/* Bottom Navigation */}
<div className="flex justify-around py-2">
<div className="flex justify-around py-2 relative">
<button
onClick={() => navigate('/nearby')}
className="flex flex-col items-center text-gray-600 p-2"
Expand All @@ -28,6 +30,35 @@ const MobileNavigation = () => {
<FiSearch size={20} />
<span className="text-xs mt-1">Search</span>
</button>
{/* Explore toggle */}
<button
onClick={() => setIsExploreOpen((v) => !v)}
className="flex flex-col items-center text-gray-600 p-2"
>
<span className="text-xs mt-1">Explore</span>
</button>
{isExploreOpen && (
<div className="absolute bottom-16 right-4 bg-white border border-gray-200 rounded-lg shadow-lg py-2 w-40">
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-med');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Med
</button>
<button
onClick={() => {
setIsExploreOpen(false);
navigate('/quick-lab');
}}
className="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-50"
>
Quick Lab
</button>
</div>
)}
<button
onClick={() => navigate('/login')}
className="flex flex-col items-center text-gray-600 p-2"
Expand Down
Loading