Refactor to manager-centric model, add team calendar for all users

Key changes:
- Removed office-centric model (deleted offices.py, office-rules)
- Renamed to team-rules, managers are part of their own team
- Team calendar visible to all (read-only for employees)
- Admins can have a manager assigned
This commit is contained in:
Stefano Manfredi
2025-12-02 13:30:04 +00:00
parent 2ad8ba3424
commit 7168fa4b72
30 changed files with 1016 additions and 910 deletions

View File

@@ -48,10 +48,16 @@
<h3>Personal Information</h3>
</div>
<div class="card-body">
<!-- LDAP Notice -->
<div id="ldapNotice" class="form-notice" style="display: none; margin-bottom: 1rem;">
<small>Your account is managed by LDAP. Some information cannot be changed here.</small>
</div>
<form id="profileForm">
<div class="form-group">
<label for="name">Full Name</label>
<input type="text" id="name" required>
<small id="nameHelp" class="text-muted" style="display: none;">Managed by LDAP</small>
</div>
<div class="form-group">
<label for="email">Email</label>
@@ -59,19 +65,24 @@
<small class="text-muted">Email cannot be changed</small>
</div>
<div class="form-group">
<label for="office">Office</label>
<select id="office">
<option value="">No office</option>
</select>
<label for="role">Role</label>
<input type="text" id="role" disabled>
<small class="text-muted">Role is assigned by your administrator</small>
</div>
<div class="form-actions">
<div class="form-group">
<label for="manager">Manager</label>
<input type="text" id="manager" disabled>
<small class="text-muted">Your manager is assigned by the administrator</small>
</div>
<div class="form-actions" id="profileActions">
<button type="submit" class="btn btn-dark">Save Changes</button>
</div>
</form>
</div>
</div>
<div class="card">
<!-- Password section - hidden for LDAP users -->
<div class="card" id="passwordCard">
<div class="card-header">
<h3>Change Password</h3>
</div>
@@ -104,36 +115,37 @@
<script src="/js/nav.js"></script>
<script>
let currentUser = null;
let isLdapUser = false;
document.addEventListener('DOMContentLoaded', async () => {
if (!api.requireAuth()) return;
currentUser = await api.getCurrentUser();
currentUser = await api.requireAuth();
if (!currentUser) return;
await loadOffices();
populateForm();
await loadProfile();
setupEventListeners();
});
async function loadOffices() {
const response = await api.get('/api/offices');
async function loadProfile() {
const response = await api.get('/api/users/me/profile');
if (response && response.ok) {
const offices = await response.json();
const select = document.getElementById('office');
offices.forEach(office => {
const option = document.createElement('option');
option.value = office.id;
option.textContent = office.name;
select.appendChild(option);
});
}
}
const profile = await response.json();
isLdapUser = profile.is_ldap_user;
function populateForm() {
document.getElementById('name').value = currentUser.name || '';
document.getElementById('email').value = currentUser.email;
document.getElementById('office').value = currentUser.office_id || '';
// Populate form
document.getElementById('name').value = profile.name || '';
document.getElementById('email').value = profile.email;
document.getElementById('role').value = profile.role;
document.getElementById('manager').value = profile.manager_name || 'None';
// LDAP mode adjustments
if (isLdapUser) {
document.getElementById('ldapNotice').style.display = 'block';
document.getElementById('name').disabled = true;
document.getElementById('nameHelp').style.display = 'block';
document.getElementById('profileActions').style.display = 'none';
document.getElementById('passwordCard').style.display = 'none';
}
}
}
function setupEventListeners() {
@@ -141,15 +153,21 @@
document.getElementById('profileForm').addEventListener('submit', async (e) => {
e.preventDefault();
if (isLdapUser) {
utils.showMessage('Profile is managed by LDAP', 'error');
return;
}
const data = {
name: document.getElementById('name').value,
office_id: document.getElementById('office').value || null
name: document.getElementById('name').value
};
const response = await api.put('/api/users/me/profile', data);
if (response && response.ok) {
utils.showMessage('Profile updated successfully', 'success');
currentUser = await api.getCurrentUser();
// Update nav display
const nameEl = document.getElementById('userName');
if (nameEl) nameEl.textContent = data.name;
} else {
const error = await response.json();
utils.showMessage(error.detail || 'Failed to update profile', 'error');