diff --git a/index.html b/index.html index 40792cc1..9ad679a7 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + medsync-react diff --git a/package-lock.json b/package-lock.json index f2e43cca..3a7a9ead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "daisyui": "^5.1.30", + "lucide-react": "^0.546.0", "react": "^19.1.1", "react-dom": "^19.1.1" }, @@ -71,7 +72,6 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -1471,7 +1471,6 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1513,7 +1512,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1737,7 +1735,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", @@ -2067,7 +2064,6 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2683,7 +2679,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -2832,6 +2827,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.546.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.546.0.tgz", + "integrity": "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3118,7 +3122,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3166,7 +3169,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3356,7 +3358,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3920,7 +3921,6 @@ "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", diff --git a/package.json b/package.json index defc898e..bb26ad6e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "daisyui": "^5.1.30", + "lucide-react": "^0.546.0", "react": "^19.1.1", "react-dom": "^19.1.1" }, diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 00000000..c25949d9 Binary files /dev/null and b/public/logo.png differ diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb1..00000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index a9fab6a3..4b271222 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,9 +10,13 @@ import Reports from './components/Reports' import Administration from './components/Administration' import Sidebar from './components/Sidebar' import { api, API_ENDPOINTS } from './config/api' +import Home from './components/Home' function App() { + const STORAGE_KEY = 'medsync_user' const [isLoggedIn, setIsLoggedIn] = useState(false) + const [isSidebarOpen, setIsSidebarOpen] = useState(false) + const [showLogin, setShowLogin] = useState(false) const [user, setUser] = useState(null) const [activeSection, setActiveSection] = useState('dashboard') const [loading, setLoading] = useState(true) @@ -22,7 +26,40 @@ function App() { checkAuthStatus() }, []) + const getStoredUser = () => { + if (typeof window === 'undefined') return null + const raw = window.localStorage.getItem(STORAGE_KEY) + if (!raw) return null + + try { + return JSON.parse(raw) + } catch (error) { + window.localStorage.removeItem(STORAGE_KEY) + console.error('Failed to parse stored user data:', error) + return null + } + } + + const persistUser = (userData) => { + if (typeof window === 'undefined') return + window.localStorage.setItem(STORAGE_KEY, JSON.stringify(userData)) + } + + const clearStoredUser = () => { + if (typeof window === 'undefined') return + window.localStorage.removeItem(STORAGE_KEY) + } + const checkAuthStatus = async () => { + const storedUser = getStoredUser() + + if (storedUser) { + setUser(storedUser) + setIsLoggedIn(true) + setActiveSection('dashboard') + setShowLogin(false) + } + try { // Add timeout to prevent infinite loading if backend is down const controller = new AbortController() @@ -34,6 +71,14 @@ function App() { if (response.isAuthenticated && response.user) { setUser(response.user) setIsLoggedIn(true) + setActiveSection('dashboard') + persistUser(response.user) + setShowLogin(false) + } else { + clearStoredUser() + setIsLoggedIn(false) + setUser(null) + setShowLogin(false) } } catch (err) { console.error('Auth check error:', err) @@ -44,8 +89,12 @@ function App() { } const handleLogin = (userData) => { + persistUser(userData) setUser(userData) setIsLoggedIn(true) + setActiveSection('dashboard') + setShowLogin(false) + setIsSidebarOpen(false) } const handleLogout = async () => { @@ -56,10 +105,18 @@ function App() { } finally { setIsLoggedIn(false) setUser(null) + clearStoredUser() setActiveSection('dashboard') + setShowLogin(false) + setIsSidebarOpen(false) } } + const handleNavigate = (section) => { + setActiveSection(section) + setIsSidebarOpen(false) + } + if (loading) { return (
@@ -69,7 +126,29 @@ function App() { } if (!isLoggedIn) { - return + if (showLogin) { + return setShowLogin(false)} /> + } + + return ( + setShowLogin(true)} + onLoginRequest={() => setShowLogin(true)} + /> + ) + } + + if (activeSection === 'home') { + return ( + handleNavigate('dashboard')} + onLogout={handleLogout} + /> + ) } const renderSection = () => { @@ -96,38 +175,72 @@ function App() { } return ( -
+
{/* Main Content */} -
+
{/* Top Bar */} -
-
- +
+
+ +
+

+ {activeSection.charAt(0).toUpperCase() + activeSection.slice(1)} +

+
-
-
+ +
+
+ +
+
-
- 5 -
{/* Content Inner */} - {renderSection()} +
+ {renderSection()} +
+ + {/* Mobile Sidebar */} + {isSidebarOpen && ( +
+
setIsSidebarOpen(false)} + role="presentation" + /> + setIsSidebarOpen(false)} + /> +
+ )}
) } diff --git a/src/assets/doctor-hero.jpg b/src/assets/doctor-hero.jpg new file mode 100644 index 00000000..95519c7c Binary files /dev/null and b/src/assets/doctor-hero.jpg differ diff --git a/src/assets/doctor-patient.jpg b/src/assets/doctor-patient.jpg new file mode 100644 index 00000000..7adb6db3 Binary files /dev/null and b/src/assets/doctor-patient.jpg differ diff --git a/src/assets/medical-professional.jpg b/src/assets/medical-professional.jpg new file mode 100644 index 00000000..7d7cec58 Binary files /dev/null and b/src/assets/medical-professional.jpg differ diff --git a/src/assets/react.svg b/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Administration.jsx b/src/components/Administration.jsx index 8ada1a02..12d37957 100644 --- a/src/components/Administration.jsx +++ b/src/components/Administration.jsx @@ -5,7 +5,6 @@ import AddStaffModal from './AddStaffModal' import AddBranchManagerModal from './AddBranchManagerModal' function Administration({ userRole = 'manager' }) { - // Set initial tab based on role - admin sees branches first, managers see staff first const initialTab = userRole === 'admin' ? 'branches-tab' : 'staff-tab' const [activeTab, setActiveTab] = useState(initialTab) const [branches, setBranches] = useState([]) @@ -69,112 +68,81 @@ function Administration({ userRole = 'manager' }) { } } - const getStatusBadge = (status) => { - return ( - - {status} - - ) - } + const getStatusBadge = (status) => ( + + {status} + + ) - return ( -
-

Administration

+ const renderLoadingState = (colSpan, message = 'Loading...') => ( + + + {message} + + + ) - {/* Admin Tabs */} -
- {userRole === 'admin' && ( -
setActiveTab('branches-tab')} - > - Branches -
- )} - {userRole === 'admin' && ( -
setActiveTab('managers-tab')} + const renderErrorState = (colSpan) => ( + + + {error} + + + ) + + const renderEmptyState = (colSpan, message) => ( + + + {message} + + + ) + + const renderBranchesTable = () => ( +
+
+
+

Branch Management

+
- )} -
setActiveTab('staff-tab')} - > - Staff -
-
setActiveTab('settings-tab')} - > - Settings + Add Branch +
-
- - {/* Branches Tab */} - {activeTab === 'branches-tab' && ( -
-
-

Branch Management

- -
- - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - - - - ) : branches.length === 0 ? ( - - - - ) : ( - branches.map((branch, index) => ( - +
Branch IDNameAddressManagerStaff CountActions
- Loading branches... -
- {error} -
- No branches found -
+ + + + + + + + + + + + {loading + ? renderLoadingState(6, 'Loading branches...') + : error + ? renderErrorState(6) + : branches.length === 0 + ? renderEmptyState(6, 'No branches found') + : branches.map((branch, index) => ( + @@ -191,65 +159,66 @@ function Administration({ userRole = 'manager' }) { - - )) - )} - -
+ Branch ID + + Name + + Address + + Manager + + Staff Count + + Actions +
BRN-{branch.id.toString().padStart(3, '0')} -
-
- )} + ))} + + +
+
+ ) - {/* Staff Tab */} - {activeTab === 'staff-tab' && ( -
-
-

Staff Management

- -
+ const renderStaffTable = () => ( +
+
+
+

Staff Management

+ +
- - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - - - - ) : staff.length === 0 ? ( - - - - ) : ( - staff.map((member, index) => ( - +
Staff IDNameRoleBranchSpecialtyStatusActions
- Loading staff... -
- {error} -
- No staff found -
+ + + + + + + + + + + + + {loading + ? renderLoadingState(7, 'Loading staff...') + : error + ? renderErrorState(7) + : staff.length === 0 + ? renderEmptyState(7, 'No staff found') + : staff.map((member, index) => ( + @@ -269,65 +238,66 @@ function Administration({ userRole = 'manager' }) { {getStatusBadge(member.is_active ? 'active' : 'inactive')} - )) - )} - -
+ Staff ID + + Name + + Role + + Branch + + Specialty + + Status + + Actions +
STF-{member.id.toString().padStart(3, '0')} -
-
- )} + ))} + + +
+
+ ) - {/* Branch Managers Tab (Admin Only) */} - {activeTab === 'managers-tab' && userRole === 'admin' && ( -
-
-

Branch Manager Management

- -
+ const renderManagersTable = () => ( +
+
+
+

Branch Manager Management

+ +
- - - - - - - - - - - - - - {loading ? ( - - - - ) : error ? ( - - - - ) : managers.length === 0 ? ( - - - - ) : ( - managers.map((manager, index) => ( - +
Manager IDNameBranchPhoneEmailStatusActions
- Loading branch managers... -
- {error} -
- No branch managers found -
+ + + + + + + + + + + + + {loading + ? renderLoadingState(7, 'Loading branch managers...') + : error + ? renderErrorState(7) + : managers.length === 0 + ? renderEmptyState(7, 'No branch managers found') + : managers.map((manager, index) => ( + @@ -347,97 +317,142 @@ function Administration({ userRole = 'manager' }) { {getStatusBadge(manager.is_active ? 'active' : 'inactive')} - )) - )} - -
+ Manager ID + + Name + + Branch + + Phone + + Email + + Status + + Actions +
MGR-{manager.id.toString().padStart(3, '0')} -
-
- )} + ))} + + +
+
+ ) - {/* Modals */} - {isAddBranchModalOpen && ( - setIsAddBranchModalOpen(false)} - onSuccess={fetchBranches} - /> - )} + return ( +
+

Administration

- {isAddStaffModalOpen && ( - setIsAddStaffModalOpen(false)} - onSuccess={fetchStaff} - /> - )} +
+ {userRole === 'admin' && ( + + )} + {userRole === 'admin' && ( + + )} + + +
- {isAddManagerModalOpen && ( - setIsAddManagerModalOpen(false)} - onSuccess={fetchManagers} - /> - )} + {activeTab === 'branches-tab' && renderBranchesTable()} + {activeTab === 'staff-tab' && renderStaffTable()} + {activeTab === 'managers-tab' && userRole === 'admin' && renderManagersTable()} - {/* Settings Tab */} {activeTab === 'settings-tab' && ( -
-

System Settings

+
+

System Settings

-
-
- - -
+
+ + +
+
-
-
-
+
-
- - -
- - +
+ +
+ +
)} + + {isAddBranchModalOpen && ( + setIsAddBranchModalOpen(false)} onSuccess={fetchBranches} /> + )} + + {isAddStaffModalOpen && ( + setIsAddStaffModalOpen(false)} onSuccess={fetchStaff} /> + )} + + {isAddManagerModalOpen && ( + setIsAddManagerModalOpen(false)} onSuccess={fetchManagers} /> + )}
) } diff --git a/src/components/Appointments.jsx b/src/components/Appointments.jsx index 36afa777..36837e34 100644 --- a/src/components/Appointments.jsx +++ b/src/components/Appointments.jsx @@ -101,34 +101,34 @@ function Appointments() { return ( <> -
-
+
+

Appointments

{/* Filters */} -
-
+
+
setDateFilter(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" />
-
+
-
+
-
+
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" />
-
- +
+