API, Backend & Frontend

This commit is contained in:
Ichitux
2026-04-01 01:18:21 +02:00
parent 331c04fbef
commit 0fe8ec9bc0
44 changed files with 10060 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
.admin-header-content {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
text-align: left;
}
.admin-header-content h1 {
margin-bottom: 0.35rem;
font-size: 2.35rem;
font-weight: 800;
color: var(--text-main);
letter-spacing: -0.02em;
}
.admin-header-content h1::after {
display: none;
}
.admin-header-content p {
color: var(--text-muted);
font-size: 1.05rem;
margin: 0;
line-height: 1.5;
}
.admin-user-info {
display: flex;
align-items: center;
gap: 1rem;
background: var(--surface);
padding: 0.5rem 1rem;
border-radius: 999px;
border: 1px solid var(--border);
box-shadow: 0 1px 2px rgba(28, 25, 23, 0.04);
}
.admin-user-info span {
font-weight: 600;
color: var(--text-main);
font-size: 0.9rem;
}
.logout-button {
background: #fef2f2;
color: #b91c1c;
border: 1px solid #fecaca;
padding: 0.4rem 0.85rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 600;
cursor: pointer;
transition: background 0.2s, border-color 0.2s;
}
.logout-button:hover {
background: #fee2e2;
border-color: #fca5a5;
}
.admin-tabs {
display: flex;
gap: 0.35rem;
margin-bottom: 2rem;
padding: 0.35rem;
background: var(--surface-muted);
border-radius: var(--radius);
border: 1px solid var(--border);
width: fit-content;
flex-wrap: wrap;
}
.admin-tab {
background: transparent;
border: none;
padding: 0.7rem 1.35rem;
border-radius: var(--radius-sm);
font-weight: 600;
font-size: 0.9rem;
color: var(--text-muted);
cursor: pointer;
transition: color 0.2s, background 0.2s, box-shadow 0.2s;
}
.admin-tab:hover {
color: var(--text-main);
}
.admin-tab.active {
background: var(--surface);
color: var(--primary);
box-shadow: 0 1px 3px rgba(28, 25, 23, 0.08);
border: 1px solid var(--border);
}
.admin-content {
background: var(--surface);
border-radius: var(--radius);
padding: 2.5rem;
border: 1px solid var(--border);
box-shadow: var(--glass-shadow);
animation: fadeInUp 0.6s ease-out;
min-height: 400px;
}
@media (max-width: 768px) {
.admin-header-content {
flex-direction: column;
text-align: center;
gap: 1rem;
}
.admin-tabs {
flex-direction: column;
width: 100%;
}
.admin-tab {
width: 100%;
text-align: center;
}
.admin-content {
padding: 1.5rem;
}
}
+131
View File
@@ -0,0 +1,131 @@
import React, { useState, useEffect } from 'react';
import '../App.css';
import './AdminView.css';
import LoginForm from '../components/admin/LoginForm';
import PharmacyManagement from '../components/admin/PharmacyManagement';
import MedicineManagement from '../components/admin/MedicineManagement';
import PharmacyMedicineLink from '../components/admin/PharmacyMedicineLink';
function AdminView() {
const [authenticated, setAuthenticated] = useState(false);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('pharmacies'); // 'pharmacies', 'medicines', 'link'
useEffect(() => {
checkAuth();
}, []);
const checkAuth = async () => {
try {
const response = await fetch('/api/auth/check', {
credentials: 'include',
});
const data = await response.json();
if (data.authenticated) {
setAuthenticated(true);
setUser(data.user);
} else {
setAuthenticated(false);
setUser(null);
}
} catch (error) {
console.error('Error checking auth:', error);
setAuthenticated(false);
} finally {
setLoading(false);
}
};
const handleLogin = (userData) => {
setAuthenticated(true);
setUser(userData);
};
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
});
setAuthenticated(false);
setUser(null);
} catch (error) {
console.error('Error logging out:', error);
}
};
if (loading) {
return (
<div className="app-main">
<div className="loading">Checking authentication...</div>
</div>
);
}
if (!authenticated) {
return (
<>
<header className="app-header">
<h1> Admin Panel</h1>
<p>Authentication required</p>
</header>
<main className="app-main">
<LoginForm onLogin={handleLogin} />
</main>
</>
);
}
return (
<>
<header className="app-header">
<div className="admin-header-content">
<div>
<h1> Admin Panel</h1>
<p>Manage pharmacies and medicines</p>
</div>
<div className="admin-user-info">
<span>👤 {user?.username}</span>
<button className="logout-button" onClick={handleLogout}>
Logout
</button>
</div>
</div>
</header>
<main className="app-main">
<div className="admin-tabs">
<button
className={`admin-tab ${activeTab === 'pharmacies' ? 'active' : ''}`}
onClick={() => setActiveTab('pharmacies')}
>
🏥 Pharmacies
</button>
<button
className={`admin-tab ${activeTab === 'medicines' ? 'active' : ''}`}
onClick={() => setActiveTab('medicines')}
>
💊 Medicines
</button>
<button
className={`admin-tab ${activeTab === 'link' ? 'active' : ''}`}
onClick={() => setActiveTab('link')}
>
🔗 Link Medicine to Pharmacy
</button>
</div>
<div className="admin-content">
{activeTab === 'pharmacies' && <PharmacyManagement />}
{activeTab === 'medicines' && <MedicineManagement />}
{activeTab === 'link' && <PharmacyMedicineLink />}
</div>
</main>
</>
);
}
export default AdminView;
+117
View File
@@ -0,0 +1,117 @@
import React, { useState, useEffect } from 'react';
import '../App.css';
import SearchBar from '../components/SearchBar';
import MedicineResults from '../components/MedicineResults';
import PharmacyList from '../components/PharmacyList';
function PublicView() {
const [searchQuery, setSearchQuery] = useState('');
const [medicines, setMedicines] = useState([]);
const [selectedMedicine, setSelectedMedicine] = useState(null);
const [pharmacies, setPharmacies] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
const searchMedicines = async () => {
if (searchQuery.trim().length < 2) {
setMedicines([]);
setSelectedMedicine(null);
setPharmacies([]);
return;
}
setLoading(true);
try {
const response = await fetch(`/api/medicines/search?q=${encodeURIComponent(searchQuery)}`);
const data = await response.json();
setMedicines(data);
} catch (error) {
console.error('Error searching medicines:', error);
} finally {
setLoading(false);
}
};
const timeoutId = setTimeout(searchMedicines, 300);
return () => clearTimeout(timeoutId);
}, [searchQuery]);
useEffect(() => {
const fetchPharmacies = async () => {
if (!selectedMedicine) {
setPharmacies([]);
return;
}
setLoading(true);
try {
const response = await fetch(`/api/medicines/${selectedMedicine.id}/pharmacies`);
const data = await response.json();
setPharmacies(data);
} catch (error) {
console.error('Error fetching pharmacies:', error);
} finally {
setLoading(false);
}
};
fetchPharmacies();
}, [selectedMedicine]);
return (
<>
<header className="app-header">
<h1>💊 FarmaFinder</h1>
<p>Find your medicine at nearby pharmacies</p>
</header>
<main className="app-main">
<SearchBar
value={searchQuery}
onChange={setSearchQuery}
placeholder="Search for a medicine..."
/>
{loading && <div className="loading">Searching...</div>}
{searchQuery && !selectedMedicine && (
<MedicineResults
medicines={medicines}
onSelect={setSelectedMedicine}
query={searchQuery}
/>
)}
{selectedMedicine && (
<div className="selected-medicine-section">
<div className="medicine-info">
<h2>{selectedMedicine.name}</h2>
<div className="medicine-details">
<span><strong>Active Ingredient:</strong> {selectedMedicine.active_ingredient}</span>
<span><strong>Dosage:</strong> {selectedMedicine.dosage}</span>
<span><strong>Form:</strong> {selectedMedicine.form}</span>
</div>
<button
className="back-button"
onClick={() => {
setSelectedMedicine(null);
setPharmacies([]);
}}
>
Back to search
</button>
</div>
<PharmacyList
pharmacies={pharmacies}
loading={loading}
/>
</div>
)}
</main>
</>
);
}
export default PublicView;