Added auth

This commit is contained in:
2026-01-21 16:38:52 -05:00
parent 615c7caee8
commit cc5c7ff42d
7 changed files with 1303 additions and 277 deletions

View File

@@ -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);
}
}

View File

@@ -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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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">&times;</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>

View File

@@ -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;
}
}