Added auth
This commit is contained in:
429
frontend/app.js
429
frontend/app.js
@@ -4,9 +4,137 @@ let currentDrug = null;
|
||||
let showLowStockOnly = false;
|
||||
let searchTerm = '';
|
||||
let expandedDrugs = new Set();
|
||||
let currentUser = null;
|
||||
let accessToken = null;
|
||||
|
||||
// Initialize
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
checkAuth();
|
||||
});
|
||||
|
||||
// Check if user is already logged in
|
||||
function checkAuth() {
|
||||
const token = localStorage.getItem('accessToken');
|
||||
const user = localStorage.getItem('currentUser');
|
||||
|
||||
if (token && user) {
|
||||
accessToken = token;
|
||||
currentUser = JSON.parse(user);
|
||||
showMainApp();
|
||||
} else {
|
||||
showLoginPage();
|
||||
}
|
||||
}
|
||||
|
||||
// Show login page
|
||||
function showLoginPage() {
|
||||
document.getElementById('loginPage').style.display = 'flex';
|
||||
document.getElementById('mainApp').style.display = 'none';
|
||||
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) loginForm.addEventListener('submit', handleLogin);
|
||||
}
|
||||
|
||||
// Show main app
|
||||
function showMainApp() {
|
||||
document.getElementById('loginPage').style.display = 'none';
|
||||
document.getElementById('mainApp').style.display = 'block';
|
||||
|
||||
const userDisplay = document.getElementById('currentUser');
|
||||
if (userDisplay) {
|
||||
userDisplay.textContent = `👤 ${currentUser.username}`;
|
||||
}
|
||||
|
||||
const adminBtn = document.getElementById('adminBtn');
|
||||
if (adminBtn) {
|
||||
adminBtn.style.display = currentUser.is_admin ? 'block' : 'none';
|
||||
}
|
||||
|
||||
setupEventListeners();
|
||||
loadDrugs();
|
||||
}
|
||||
|
||||
// Handle login
|
||||
async function handleLogin(e) {
|
||||
e.preventDefault();
|
||||
const username = document.getElementById('loginUsername').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
accessToken = data.access_token;
|
||||
currentUser = data.user;
|
||||
|
||||
localStorage.setItem('accessToken', accessToken);
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
|
||||
document.getElementById('loginForm').reset();
|
||||
const errorDiv = document.getElementById('loginError');
|
||||
if (errorDiv) errorDiv.style.display = 'none';
|
||||
showMainApp();
|
||||
} catch (error) {
|
||||
const errorDiv = document.getElementById('loginError');
|
||||
if (errorDiv) {
|
||||
errorDiv.textContent = error.message;
|
||||
errorDiv.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle register
|
||||
// Logout
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('currentUser');
|
||||
accessToken = null;
|
||||
currentUser = null;
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
if (loginForm) loginForm.reset();
|
||||
const registerForm = document.getElementById('registerForm');
|
||||
if (registerForm) {
|
||||
registerForm.style.display = 'none';
|
||||
}
|
||||
const form = document.getElementById('loginForm');
|
||||
if (form) form.style.display = 'block';
|
||||
showLoginPage();
|
||||
}
|
||||
|
||||
// API helper with authentication
|
||||
async function apiCall(endpoint, options = {}) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
};
|
||||
|
||||
if (accessToken) {
|
||||
headers['Authorization'] = `Bearer ${accessToken}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}${endpoint}`, {
|
||||
...options,
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
handleLogout();
|
||||
throw new Error('Authentication expired');
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
// Setup event listeners
|
||||
function setupEventListeners() {
|
||||
const drugForm = document.getElementById('drugForm');
|
||||
const variantForm = document.getElementById('variantForm');
|
||||
const editVariantForm = document.getElementById('editVariantForm');
|
||||
@@ -18,6 +146,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const dispenseModal = document.getElementById('dispenseModal');
|
||||
const editModal = document.getElementById('editModal');
|
||||
const addDrugBtn = document.getElementById('addDrugBtn');
|
||||
const dispenseBtn = document.getElementById('dispenseBtn');
|
||||
const cancelAddBtn = document.getElementById('cancelAddBtn');
|
||||
const cancelVariantBtn = document.getElementById('cancelVariantBtn');
|
||||
const cancelEditVariantBtn = document.getElementById('cancelEditVariantBtn');
|
||||
@@ -25,41 +154,75 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const cancelEditBtn = document.getElementById('cancelEditBtn');
|
||||
const showAllBtn = document.getElementById('showAllBtn');
|
||||
const showLowStockBtn = document.getElementById('showLowStockBtn');
|
||||
const userMenuBtn = document.getElementById('userMenuBtn');
|
||||
const adminBtn = document.getElementById('adminBtn');
|
||||
const logoutBtn = document.getElementById('logoutBtn');
|
||||
const changePasswordBtn = document.getElementById('changePasswordBtn');
|
||||
|
||||
// Modal close buttons
|
||||
const closeButtons = document.querySelectorAll('.close');
|
||||
|
||||
drugForm.addEventListener('submit', handleAddDrug);
|
||||
variantForm.addEventListener('submit', handleAddVariant);
|
||||
editVariantForm.addEventListener('submit', handleEditVariant);
|
||||
dispenseForm.addEventListener('submit', handleDispenseDrug);
|
||||
editForm.addEventListener('submit', handleEditDrug);
|
||||
if (drugForm) drugForm.addEventListener('submit', handleAddDrug);
|
||||
if (variantForm) variantForm.addEventListener('submit', handleAddVariant);
|
||||
if (editVariantForm) editVariantForm.addEventListener('submit', handleEditVariant);
|
||||
if (dispenseForm) dispenseForm.addEventListener('submit', handleDispenseDrug);
|
||||
if (editForm) editForm.addEventListener('submit', handleEditDrug);
|
||||
|
||||
addDrugBtn.addEventListener('click', () => openModal(addModal));
|
||||
if (addDrugBtn) addDrugBtn.addEventListener('click', () => openModal(addModal));
|
||||
if (dispenseBtn) dispenseBtn.addEventListener('click', () => {
|
||||
updateDispenseDrugSelect();
|
||||
openModal(dispenseModal);
|
||||
});
|
||||
|
||||
if (cancelAddBtn) cancelAddBtn.addEventListener('click', () => closeModal(addModal));
|
||||
if (cancelVariantBtn) cancelVariantBtn.addEventListener('click', () => closeModal(addVariantModal));
|
||||
if (cancelEditVariantBtn) cancelEditVariantBtn.addEventListener('click', () => closeModal(editVariantModal));
|
||||
if (cancelDispenseBtn) cancelDispenseBtn.addEventListener('click', () => closeModal(dispenseModal));
|
||||
if (cancelEditBtn) cancelEditBtn.addEventListener('click', closeEditModal);
|
||||
|
||||
cancelAddBtn.addEventListener('click', () => closeModal(addModal));
|
||||
cancelVariantBtn.addEventListener('click', () => closeModal(addVariantModal));
|
||||
cancelEditVariantBtn.addEventListener('click', () => closeModal(editVariantModal));
|
||||
cancelDispenseBtn.addEventListener('click', () => closeModal(dispenseModal));
|
||||
cancelEditBtn.addEventListener('click', closeEditModal);
|
||||
const closeHistoryBtn = document.getElementById('closeHistoryBtn');
|
||||
closeHistoryBtn.addEventListener('click', () => closeModal(document.getElementById('historyModal')));
|
||||
if (closeHistoryBtn) closeHistoryBtn.addEventListener('click', () => closeModal(document.getElementById('historyModal')));
|
||||
|
||||
const closeUserManagementBtn = document.getElementById('closeUserManagementBtn');
|
||||
if (closeUserManagementBtn) closeUserManagementBtn.addEventListener('click', () => closeModal(document.getElementById('userManagementModal')));
|
||||
|
||||
const changePasswordForm = document.getElementById('changePasswordForm');
|
||||
if (changePasswordForm) changePasswordForm.addEventListener('submit', handleChangePassword);
|
||||
|
||||
const cancelChangePasswordBtn = document.getElementById('cancelChangePasswordBtn');
|
||||
if (cancelChangePasswordBtn) cancelChangePasswordBtn.addEventListener('click', () => closeModal(document.getElementById('changePasswordModal')));
|
||||
|
||||
const adminChangePasswordForm = document.getElementById('adminChangePasswordForm');
|
||||
if (adminChangePasswordForm) adminChangePasswordForm.addEventListener('submit', handleAdminChangePassword);
|
||||
|
||||
const cancelAdminChangePasswordBtn = document.getElementById('cancelAdminChangePasswordBtn');
|
||||
if (cancelAdminChangePasswordBtn) cancelAdminChangePasswordBtn.addEventListener('click', () => closeModal(document.getElementById('adminChangePasswordModal')));
|
||||
|
||||
closeButtons.forEach(btn => btn.addEventListener('click', (e) => {
|
||||
const modal = e.target.closest('.modal');
|
||||
closeModal(modal);
|
||||
}));
|
||||
|
||||
showAllBtn.addEventListener('click', () => {
|
||||
if (showAllBtn) showAllBtn.addEventListener('click', () => {
|
||||
showLowStockOnly = false;
|
||||
updateFilterButtons();
|
||||
renderDrugs();
|
||||
});
|
||||
showLowStockBtn.addEventListener('click', () => {
|
||||
if (showLowStockBtn) showLowStockBtn.addEventListener('click', () => {
|
||||
showLowStockOnly = true;
|
||||
updateFilterButtons();
|
||||
renderDrugs();
|
||||
});
|
||||
|
||||
// User menu
|
||||
if (userMenuBtn) userMenuBtn.addEventListener('click', () => {
|
||||
const dropdown = document.getElementById('userDropdown');
|
||||
if (dropdown) dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
|
||||
});
|
||||
|
||||
if (changePasswordBtn) changePasswordBtn.addEventListener('click', openChangePasswordModal);
|
||||
if (adminBtn) adminBtn.addEventListener('click', openUserManagement);
|
||||
if (logoutBtn) logoutBtn.addEventListener('click', handleLogout);
|
||||
|
||||
// Search functionality
|
||||
const drugSearch = document.getElementById('drugSearch');
|
||||
@@ -80,14 +243,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
closeModal(e.target);
|
||||
}
|
||||
});
|
||||
|
||||
loadDrugs();
|
||||
});
|
||||
}
|
||||
|
||||
// Load drugs from API
|
||||
async function loadDrugs() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/drugs`);
|
||||
const response = await apiCall('/drugs');
|
||||
if (!response.ok) throw new Error('Failed to load drugs');
|
||||
allDrugs = await response.json();
|
||||
|
||||
@@ -96,18 +257,32 @@ async function loadDrugs() {
|
||||
} catch (error) {
|
||||
console.error('Error loading drugs:', error);
|
||||
document.getElementById('drugsList').innerHTML =
|
||||
'<p class="empty">Error loading drugs. Make sure the backend is running on http://localhost:8000</p>';
|
||||
'<p class="empty">Error loading drugs. Make sure the backend is running.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Modal utility functions
|
||||
function openModal(modal) {
|
||||
// Find the highest z-index among currently visible modals
|
||||
const visibleModals = document.querySelectorAll('.modal.show');
|
||||
let maxZIndex = 1000;
|
||||
|
||||
visibleModals.forEach(m => {
|
||||
const zIndex = parseInt(window.getComputedStyle(m).zIndex, 10) || 1000;
|
||||
if (zIndex > maxZIndex) {
|
||||
maxZIndex = zIndex;
|
||||
}
|
||||
});
|
||||
|
||||
// Set the new modal's z-index higher than any existing modal
|
||||
modal.style.zIndex = (maxZIndex + 100).toString();
|
||||
modal.classList.add('show');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeModal(modal) {
|
||||
modal.classList.remove('show');
|
||||
modal.style.zIndex = '1000';
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
@@ -224,9 +399,8 @@ async function handleAddDrug(e) {
|
||||
|
||||
try {
|
||||
// Create the drug first
|
||||
const drugResponse = await fetch(`${API_URL}/drugs`, {
|
||||
const drugResponse = await apiCall('/drugs', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(drugData)
|
||||
});
|
||||
|
||||
@@ -243,9 +417,8 @@ async function handleAddDrug(e) {
|
||||
low_stock_threshold: parseFloat(document.getElementById('initialVariantThreshold').value) || 10
|
||||
};
|
||||
|
||||
const variantResponse = await fetch(`${API_URL}/drugs/${createdDrug.id}/variants`, {
|
||||
const variantResponse = await apiCall(`/drugs/${createdDrug.id}/variants`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(variantData)
|
||||
});
|
||||
|
||||
@@ -282,15 +455,14 @@ async function handleDispenseDrug(e) {
|
||||
const dispensingData = {
|
||||
drug_variant_id: variantId,
|
||||
quantity: quantity,
|
||||
animal_name: animalName,
|
||||
animal_name: animalName || null,
|
||||
user_name: userName,
|
||||
notes: notes || null
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/dispense`, {
|
||||
const response = await apiCall('/dispense', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(dispensingData)
|
||||
});
|
||||
|
||||
@@ -360,9 +532,8 @@ async function handleAddVariant(e) {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/drugs/${drugId}/variants`, {
|
||||
const response = await apiCall(`/drugs/${drugId}/variants`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(variantData)
|
||||
});
|
||||
|
||||
@@ -412,9 +583,8 @@ async function handleEditVariant(e) {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/variants/${variantId}`, {
|
||||
const response = await apiCall(`/variants/${variantId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(variantData)
|
||||
});
|
||||
|
||||
@@ -448,7 +618,7 @@ async function deleteVariant(variantId) {
|
||||
if (!confirm('Are you sure you want to delete this variant?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/variants/${variantId}`, {
|
||||
const response = await apiCall(`/variants/${variantId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
@@ -476,7 +646,7 @@ async function showDrugHistory(drugId) {
|
||||
openModal(historyModal);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/dispense/history`);
|
||||
const response = await apiCall(`/dispense/history`);
|
||||
if (!response.ok) throw new Error('Failed to fetch history');
|
||||
|
||||
const allHistory = await response.json();
|
||||
@@ -546,9 +716,8 @@ async function handleEditDrug(e) {
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/drugs/${drugId}`, {
|
||||
const response = await apiCall(`/drugs/${drugId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(drugData)
|
||||
});
|
||||
|
||||
@@ -568,7 +737,7 @@ async function deleteDrug(drugId) {
|
||||
if (!confirm('Are you sure you want to delete this drug?')) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/drugs/${drugId}`, {
|
||||
const response = await apiCall(`/drugs/${drugId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
@@ -582,6 +751,104 @@ async function deleteDrug(drugId) {
|
||||
}
|
||||
}
|
||||
|
||||
// Password Management
|
||||
function openChangePasswordModal() {
|
||||
const modal = document.getElementById('changePasswordModal');
|
||||
document.getElementById('changePasswordForm').reset();
|
||||
|
||||
// Close dropdown
|
||||
const dropdown = document.getElementById('userDropdown');
|
||||
if (dropdown) dropdown.style.display = 'none';
|
||||
|
||||
openModal(modal);
|
||||
}
|
||||
|
||||
async function handleChangePassword(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const currentPassword = document.getElementById('currentPassword').value;
|
||||
const newPassword = document.getElementById('newPassword').value;
|
||||
const confirmPassword = document.getElementById('confirmNewPassword').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('New passwords do not match!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 1) {
|
||||
alert('New password cannot be empty!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall('/auth/change-password', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
current_password: currentPassword,
|
||||
new_password: newPassword
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to change password');
|
||||
}
|
||||
|
||||
alert('Password changed successfully!');
|
||||
closeModal(document.getElementById('changePasswordModal'));
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
alert('Failed to change password: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function openAdminChangePasswordModal(userId, username) {
|
||||
const modal = document.getElementById('adminChangePasswordModal');
|
||||
document.getElementById('adminChangePasswordForm').reset();
|
||||
document.getElementById('adminChangePasswordUserId').value = userId;
|
||||
document.getElementById('adminChangePasswordUsername').value = username;
|
||||
openModal(modal);
|
||||
}
|
||||
|
||||
async function handleAdminChangePassword(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const userId = document.getElementById('adminChangePasswordUserId').value;
|
||||
const newPassword = document.getElementById('adminChangePasswordNewPassword').value;
|
||||
const confirmPassword = document.getElementById('adminChangePasswordConfirm').value;
|
||||
|
||||
if (newPassword !== confirmPassword) {
|
||||
alert('Passwords do not match!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (newPassword.length < 1) {
|
||||
alert('Password cannot be empty!');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/users/${userId}/change-password`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
new_password: newPassword
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to change password');
|
||||
}
|
||||
|
||||
alert('Password changed successfully!');
|
||||
closeModal(document.getElementById('adminChangePasswordModal'));
|
||||
openUserManagement();
|
||||
} catch (error) {
|
||||
console.error('Error changing password:', error);
|
||||
alert('Failed to change password: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Update filter button states
|
||||
function updateFilterButtons() {
|
||||
document.getElementById('showAllBtn').classList.toggle('active', !showLowStockOnly);
|
||||
@@ -594,3 +861,93 @@ function escapeHtml(text) {
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// User Management
|
||||
async function openUserManagement() {
|
||||
const modal = document.getElementById('userManagementModal');
|
||||
document.getElementById('newUsername').value = '';
|
||||
document.getElementById('newUserPassword').value = '';
|
||||
|
||||
const usersList = document.getElementById('usersList');
|
||||
usersList.innerHTML = '<h3>Users</h3><p class="loading">Loading users...</p>';
|
||||
|
||||
try {
|
||||
const response = await apiCall('/users');
|
||||
if (!response.ok) throw new Error('Failed to load users');
|
||||
|
||||
const users = await response.json();
|
||||
|
||||
const usersHtml = `
|
||||
<h3>Users</h3>
|
||||
<div class="users-table">
|
||||
${users.map(user => `
|
||||
<div class="user-item">
|
||||
<span>${user.username}</span>
|
||||
<span class="admin-badge">${user.is_admin ? '👑 Admin' : 'User'}</span>
|
||||
<button class="btn btn-secondary btn-small" onclick="openAdminChangePasswordModal(${user.id}, '${escapeHtml(user.username)}')">🔑 Password</button>
|
||||
${user.id !== currentUser.id ? `
|
||||
<button class="btn btn-danger btn-small" onclick="deleteUser(${user.id})">Delete</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`;
|
||||
|
||||
usersList.innerHTML = usersHtml;
|
||||
} catch (error) {
|
||||
console.error('Error loading users:', error);
|
||||
usersList.innerHTML = '<h3>Users</h3><p class="empty">Error loading users</p>';
|
||||
}
|
||||
|
||||
const createUserForm = document.getElementById('createUserForm');
|
||||
if (createUserForm) {
|
||||
createUserForm.onsubmit = createUser;
|
||||
}
|
||||
|
||||
openModal(modal);
|
||||
}
|
||||
|
||||
// Create user
|
||||
async function createUser(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('newUsername').value;
|
||||
const password = document.getElementById('newUserPassword').value;
|
||||
|
||||
try {
|
||||
const response = await apiCall('/users', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'Failed to create user');
|
||||
}
|
||||
|
||||
document.getElementById('newUsername').value = '';
|
||||
document.getElementById('newUserPassword').value = '';
|
||||
alert('User created successfully!');
|
||||
openUserManagement();
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error);
|
||||
alert('Failed to create user: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete user
|
||||
async function deleteUser(userId) {
|
||||
if (!confirm('Are you sure you want to delete this user?')) return;
|
||||
|
||||
try {
|
||||
const response = await apiCall(`/users/${userId}`, { method: 'DELETE' });
|
||||
|
||||
if (!response.ok) throw new Error('Failed to delete user');
|
||||
|
||||
alert('User deleted successfully!');
|
||||
openUserManagement();
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
alert('Failed to delete user: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,53 @@
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<!-- Login Page -->
|
||||
<div id="loginPage" class="login-page">
|
||||
<div class="login-container">
|
||||
<h1>🐶 MTAR Drug Inventory System 🐶</h1>
|
||||
</header>
|
||||
<form id="loginForm" class="login-form">
|
||||
<h2>Login</h2>
|
||||
<div class="form-group">
|
||||
<label for="loginUsername">Username</label>
|
||||
<input type="text" id="loginUsername" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="loginPassword">Password</label>
|
||||
<input type="password" id="loginPassword" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Login</button>
|
||||
<p class="login-info">Contact an administrator to create an account</p>
|
||||
</form>
|
||||
<div id="loginError" class="error-message" style="display: none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main>
|
||||
<!-- Drug List Section -->
|
||||
<section id="listSection" class="list-section">
|
||||
<div class="section-header">
|
||||
<h2>Current Inventory</h2>
|
||||
<div class="header-actions">
|
||||
<button id="addDrugBtn" class="btn btn-primary btn-small">➕ Add Drug</button>
|
||||
<!-- Main App (hidden until logged in) -->
|
||||
<div id="mainApp" class="main-app" style="display: none;">
|
||||
<div class="container">
|
||||
<header>
|
||||
<div class="header-top">
|
||||
<h1>🐶 MTAR Drug Inventory System 🐶</h1>
|
||||
<div class="user-menu">
|
||||
<span id="currentUser">User</span>
|
||||
<button id="userMenuBtn" class="btn btn-small">⋮</button>
|
||||
<div id="userDropdown" class="user-dropdown" style="display: none;">
|
||||
<button id="changePasswordBtn" class="dropdown-item">🔑 Change Password</button>
|
||||
<button id="adminBtn" class="dropdown-item" style="display: none;">👤 Admin</button>
|
||||
<button id="logoutBtn" class="dropdown-item">🚪 Logout</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Drug List Section -->
|
||||
<section id="listSection" class="list-section">
|
||||
<div class="section-header">
|
||||
<h2>Current Inventory</h2>
|
||||
<div class="header-actions">
|
||||
<button id="addDrugBtn" class="btn btn-primary btn-small">➕ Add Drug</button>
|
||||
</div>
|
||||
<div class="filters">
|
||||
<button id="showAllBtn" class="filter-btn active">All</button>
|
||||
<button id="showLowStockBtn" class="filter-btn">Low Stock Only</button>
|
||||
@@ -40,219 +74,307 @@
|
||||
<footer>
|
||||
<p>Many Tears Confidential</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Edit Drug Modal -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Edit Drug</h2>
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editDrugId">
|
||||
<div class="form-group">
|
||||
<label for="editDrugName">Drug Name *</label>
|
||||
<input type="text" id="editDrugName" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editDrugDescription">Description</label>
|
||||
<input type="text" id="editDrugDescription">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelEditBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Drug Modal -->
|
||||
<div id="addModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Add New Drug</h2>
|
||||
<form id="drugForm">
|
||||
<div class="form-group">
|
||||
<label for="drugName">Drug Name *</label>
|
||||
<input type="text" id="drugName" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="drugDescription">Description</label>
|
||||
<input type="text" id="drugDescription">
|
||||
</div>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;">
|
||||
<h3 style="margin-top: 0;">Initial Variant (Optional)</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantStrength">Strength</label>
|
||||
<input type="text" id="initialVariantStrength" placeholder="e.g., 10mg, 5.4mg">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantQuantity">Quantity</label>
|
||||
<input type="number" id="initialVariantQuantity" placeholder="0" min="0" step="0.1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantUnit">Unit</label>
|
||||
<input type="text" id="initialVariantUnit" placeholder="e.g., tablets, capsules, vials" value="units">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantThreshold">Low Stock Threshold</label>
|
||||
<input type="number" id="initialVariantThreshold" placeholder="0" min="0" step="0.1" value="10">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Add Drug</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelAddBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dispense Drug Modal -->
|
||||
<div id="dispenseModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Dispense Drug</h2>
|
||||
<form id="dispenseForm" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="dispenseDrugSelect">Drug Variant *</label>
|
||||
<select id="dispenseDrugSelect">
|
||||
<option value="">-- Select a drug variant --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseQuantity">Quantity *</label>
|
||||
<input type="number" id="dispenseQuantity" step="0.1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseAnimal">Animal Name/ID</label>
|
||||
<input type="text" id="dispenseAnimal">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseUser">Dispensed by *</label>
|
||||
<input type="text" id="dispenseUser">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseNotes">Notes</label>
|
||||
<input type="text" id="dispenseNotes" placeholder="Optional">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Dispense</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelDispenseBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Variant Modal -->
|
||||
<div id="addVariantModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Add Variant</h2>
|
||||
<form id="variantForm">
|
||||
<input type="hidden" id="variantDrugId">
|
||||
<div class="form-group">
|
||||
<label for="variantStrength">Strength *</label>
|
||||
<input type="text" id="variantStrength" placeholder="e.g., 5.4mg, 10.8mg, 100ml" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<!-- Edit Drug Modal -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Edit Drug</h2>
|
||||
<form id="editForm">
|
||||
<input type="hidden" id="editDrugId">
|
||||
<div class="form-group">
|
||||
<label for="variantQuantity">Quantity *</label>
|
||||
<input type="number" id="variantQuantity" step="0.1" required>
|
||||
<label for="editDrugName">Drug Name *</label>
|
||||
<input type="text" id="editDrugName" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="variantUnit">Unit *</label>
|
||||
<select id="variantUnit">
|
||||
<option value="tablets">Tablets</option>
|
||||
<option value="bottles">Bottles</option>
|
||||
<option value="ml">ml</option>
|
||||
<option value="vials">Vials</option>
|
||||
<option value="units">Units</option>
|
||||
<option value="packets">Packets</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="variantThreshold">Low Stock Threshold *</label>
|
||||
<input type="number" id="variantThreshold" step="0.1" value="10" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Add Variant</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelVariantBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Variant Modal -->
|
||||
<div id="editVariantModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Edit Variant</h2>
|
||||
<form id="editVariantForm">
|
||||
<input type="hidden" id="editVariantId">
|
||||
<div class="form-group">
|
||||
<label for="editVariantStrength">Strength *</label>
|
||||
<input type="text" id="editVariantStrength" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="editVariantQuantity">Quantity *</label>
|
||||
<input type="number" id="editVariantQuantity" step="0.1" required>
|
||||
<label for="editDrugDescription">Description</label>
|
||||
<input type="text" id="editDrugDescription">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editVariantUnit">Unit *</label>
|
||||
<select id="editVariantUnit">
|
||||
<option value="tablets">Tablets</option>
|
||||
<option value="bottles">Bottles</option>
|
||||
<option value="ml">ml</option>
|
||||
<option value="vials">Vials</option>
|
||||
<option value="units">Units</option>
|
||||
<option value="packets">Packets</option>
|
||||
</select>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelEditBtn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editVariantThreshold">Low Stock Threshold *</label>
|
||||
<input type="number" id="editVariantThreshold" step="0.1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelEditVariantBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dispensing History Modal -->
|
||||
<div id="historyModal" class="modal">
|
||||
<div class="modal-content modal-large">
|
||||
<span class="close">×</span>
|
||||
<h2>Dispensing History - <span id="historyDrugName"></span></h2>
|
||||
<div id="historyContent" class="history-content">
|
||||
<p class="loading">Loading history...</p>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" id="closeHistoryBtn">Close</button>
|
||||
</div>
|
||||
|
||||
<!-- Add Drug Modal -->
|
||||
<div id="addModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Add New Drug</h2>
|
||||
<form id="drugForm">
|
||||
<div class="form-group">
|
||||
<label for="drugName">Drug Name *</label>
|
||||
<input type="text" id="drugName" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="drugDescription">Description</label>
|
||||
<input type="text" id="drugDescription">
|
||||
</div>
|
||||
|
||||
<hr style="margin: 20px 0; border: none; border-top: 1px solid #ddd;">
|
||||
<h3 style="margin-top: 0;">Initial Variant (Optional)</h3>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantStrength">Strength</label>
|
||||
<input type="text" id="initialVariantStrength" placeholder="e.g., 10mg, 5.4mg">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantQuantity">Quantity</label>
|
||||
<input type="number" id="initialVariantQuantity" placeholder="0" min="0" step="0.1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantUnit">Unit</label>
|
||||
<select id="initialVariantUnit">
|
||||
<option value="tablets">Tablets</option>
|
||||
<option value="bottles">Bottles</option>
|
||||
<option value="boxes">Boxes</option>
|
||||
<option value="vials">Vials</option>
|
||||
<option value="units">Units</option>
|
||||
<option value="packets">Packets</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="initialVariantThreshold">Low Stock Threshold</label>
|
||||
<input type="number" id="initialVariantThreshold" placeholder="0" min="0" step="0.1" value="10">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Add Drug</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelAddBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dispense Drug Modal -->
|
||||
<div id="dispenseModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Dispense Drug</h2>
|
||||
<form id="dispenseForm" novalidate>
|
||||
<div class="form-group">
|
||||
<label for="dispenseDrugSelect">Drug Variant *</label>
|
||||
<select id="dispenseDrugSelect">
|
||||
<option value="">-- Select a drug variant --</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseQuantity">Quantity *</label>
|
||||
<input type="number" id="dispenseQuantity" step="0.1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseAnimal">Animal Name/ID</label>
|
||||
<input type="text" id="dispenseAnimal">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseUser">Dispensed by *</label>
|
||||
<input type="text" id="dispenseUser">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="dispenseNotes">Notes</label>
|
||||
<input type="text" id="dispenseNotes" placeholder="Optional">
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Dispense</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelDispenseBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Variant Modal -->
|
||||
<div id="addVariantModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Add Variant</h2>
|
||||
<form id="variantForm">
|
||||
<input type="hidden" id="variantDrugId">
|
||||
<div class="form-group">
|
||||
<label for="variantStrength">Strength *</label>
|
||||
<input type="text" id="variantStrength" placeholder="e.g., 5.4mg, 10.8mg, 100ml" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="variantQuantity">Quantity *</label>
|
||||
<input type="number" id="variantQuantity" step="0.1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="variantUnit">Unit *</label>
|
||||
<select id="variantUnit">
|
||||
<option value="tablets">Tablets</option>
|
||||
<option value="bottles">Bottles</option>
|
||||
<option value="boxes">boxes</option>
|
||||
<option value="vials">Vials</option>
|
||||
<option value="units">Units</option>
|
||||
<option value="packets">Packets</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="variantThreshold">Low Stock Threshold *</label>
|
||||
<input type="number" id="variantThreshold" step="0.1" value="10" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Add Variant</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelVariantBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Variant Modal -->
|
||||
<div id="editVariantModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Edit Variant</h2>
|
||||
<form id="editVariantForm">
|
||||
<input type="hidden" id="editVariantId">
|
||||
<div class="form-group">
|
||||
<label for="editVariantStrength">Strength *</label>
|
||||
<input type="text" id="editVariantStrength" required>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="editVariantQuantity">Quantity *</label>
|
||||
<input type="number" id="editVariantQuantity" step="0.1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editVariantUnit">Unit *</label>
|
||||
<select id="editVariantUnit">
|
||||
<option value="tablets">Tablets</option>
|
||||
<option value="bottles">Bottles</option>
|
||||
<option value="boxes">Boxes</option>
|
||||
<option value="vials">Vials</option>
|
||||
<option value="units">Units</option>
|
||||
<option value="packets">Packets</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editVariantThreshold">Low Stock Threshold *</label>
|
||||
<input type="number" id="editVariantThreshold" step="0.1" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Save Changes</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelEditVariantBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dispensing History Modal -->
|
||||
<div id="historyModal" class="modal">
|
||||
<div class="modal-content modal-large">
|
||||
<span class="close">×</span>
|
||||
<h2>Dispensing History - <span id="historyDrugName"></span></h2>
|
||||
<div id="historyContent" class="history-content">
|
||||
<p class="loading">Loading history...</p>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" id="closeHistoryBtn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Change Password Modal -->
|
||||
<div id="changePasswordModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Change Password</h2>
|
||||
<form id="changePasswordForm">
|
||||
<div class="form-group">
|
||||
<label for="currentPassword">Current Password *</label>
|
||||
<input type="password" id="currentPassword" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newPassword">New Password *</label>
|
||||
<input type="password" id="newPassword" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmNewPassword">Confirm New Password *</label>
|
||||
<input type="password" id="confirmNewPassword" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Change Password</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelChangePasswordBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Admin Change User Password Modal -->
|
||||
<div id="adminChangePasswordModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>Change User Password</h2>
|
||||
<form id="adminChangePasswordForm">
|
||||
<input type="hidden" id="adminChangePasswordUserId">
|
||||
<div class="form-group">
|
||||
<label for="adminChangePasswordUsername">Username</label>
|
||||
<input type="text" id="adminChangePasswordUsername" disabled>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="adminChangePasswordNewPassword">New Password *</label>
|
||||
<input type="password" id="adminChangePasswordNewPassword" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="adminChangePasswordConfirm">Confirm Password *</label>
|
||||
<input type="password" id="adminChangePasswordConfirm" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">Set Password</button>
|
||||
<button type="button" class="btn btn-secondary" id="cancelAdminChangePasswordBtn">Cancel</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Management Modal -->
|
||||
<div id="userManagementModal" class="modal">
|
||||
<div class="modal-content modal-large">
|
||||
<span class="close">×</span>
|
||||
<h2>User Management</h2>
|
||||
<div class="user-management-content">
|
||||
<div class="form-group">
|
||||
<h3>Create New User</h3>
|
||||
<form id="createUserForm">
|
||||
<div class="form-row">
|
||||
<input type="text" id="newUsername" placeholder="Username" required>
|
||||
<input type="password" id="newUserPassword" placeholder="Password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-small">Create User</button>
|
||||
</form>
|
||||
</div>
|
||||
<div id="usersList" class="users-list">
|
||||
<h3>Users</h3>
|
||||
<p class="loading">Loading users...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-secondary" id="closeUserManagementBtn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -24,6 +24,109 @@ body {
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Login Page Styles */
|
||||
.login-page {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background: var(--white);
|
||||
padding: 40px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-container h1 {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.login-form.active,
|
||||
.login-form:first-of-type {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.login-form h2 {
|
||||
margin-bottom: 20px;
|
||||
color: var(--primary-color);
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.login-form .form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-form label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.login-form input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
.login-form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--secondary-color);
|
||||
box-shadow: 0 0 5px rgba(52, 152, 219, 0.3);
|
||||
}
|
||||
|
||||
.login-toggle {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.login-toggle button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--secondary-color);
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.login-info {
|
||||
text-align: center;
|
||||
margin-top: 15px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-light);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #ffebee;
|
||||
color: var(--danger-color);
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 4px solid var(--danger-color);
|
||||
}
|
||||
|
||||
/* Main App Styles */
|
||||
.main-app {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
@@ -40,14 +143,80 @@ header {
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.header-top h1 {
|
||||
flex: 1;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-menu span {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#userMenuBtn {
|
||||
background: rgba(255,255,255,0.2);
|
||||
color: var(--white);
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
#userMenuBtn:hover {
|
||||
background: rgba(255,255,255,0.3);
|
||||
}
|
||||
|
||||
.user-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--white);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
min-width: 150px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
border: none;
|
||||
background: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
font-size: 1em;
|
||||
color: var(--text-dark);
|
||||
border-bottom: 1px solid var(--light-bg);
|
||||
}
|
||||
|
||||
.dropdown-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--light-bg);
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
main {
|
||||
@@ -82,6 +251,7 @@ label {
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
input[type="password"],
|
||||
select {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
@@ -94,6 +264,7 @@ select {
|
||||
|
||||
input[type="text"]:focus,
|
||||
input[type="number"]:focus,
|
||||
input[type="password"]:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: var(--secondary-color);
|
||||
@@ -191,6 +362,9 @@ select:focus {
|
||||
|
||||
.header-actions {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.filters {
|
||||
@@ -670,3 +844,106 @@ footer {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* User Management Styles */
|
||||
.user-management-content {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.user-management-content h3 {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 15px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.user-management-content .form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.user-management-content input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.user-management-content .btn {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.users-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #f8f9fa;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.admin-badge {
|
||||
padding: 4px 12px;
|
||||
background: var(--warning-color);
|
||||
color: var(--white);
|
||||
border-radius: 20px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 768px) {
|
||||
header {
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.header-top {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header-top h1 {
|
||||
flex: none;
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 90%;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.drug-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.drug-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.variant-item {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.variant-actions {
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user