Files
TimeTrack/templates/team_hours.html
Jens Luedicke 38a51c3eed Refactor and enhance export functionality with Team Hours support
- Fix missing import statements for CSV/Excel export functionality
- Refactor export code into modular helper functions for better maintainability
- Add comprehensive Team Hours export feature with CSV and Excel support
- Enhance export UI styling with modern gradients and hover effects
- Add role-based access control for team export functionality
- Include date range filtering and team leader inclusion options
- Add proper error handling and user feedback for export operations
- Update dependencies to include pandas and xlsxwriter
- Fix JavaScript scope issues for export button functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-29 16:24:55 +02:00

238 lines
9.9 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<div class="timetrack-container">
<h2>Team Hours</h2>
<div class="date-filter">
<form id="date-range-form" method="GET" action="{{ url_for('team_hours') }}">
<div class="form-group">
<label for="start-date">Start Date:</label>
<input type="date" id="start-date" name="start_date" value="{{ start_date.strftime('%Y-%m-%d') }}">
</div>
<div class="form-group">
<label for="end-date">End Date:</label>
<input type="date" id="end-date" name="end_date" value="{{ end_date.strftime('%Y-%m-%d') }}">
</div>
<div class="form-group">
<label for="include-self">
<input type="checkbox" id="include-self" name="include_self" {% if request.args.get('include_self') %}checked{% endif %}> Include my hours
</label>
</div>
<button type="submit" class="btn">Apply Filter</button>
</form>
</div>
<!-- Export Buttons -->
<div class="export-button-container" id="export-buttons" style="display: none;">
<h4>Export Team Hours</h4>
<div class="quick-export-buttons">
<button class="btn" onclick="exportTeamHours('csv')">Export as CSV</button>
<button class="btn" onclick="exportTeamHours('excel')">Export as Excel</button>
</div>
</div>
<div class="team-hours-container">
<div id="loading">Loading team data...</div>
<div id="team-info" style="display: none;">
<h3>Team: <span id="team-name"></span></h3>
<p id="team-description"></p>
</div>
<div id="team-hours-table" style="display: none;">
<table class="time-history">
<thead id="table-header">
<tr>
<th>Team Member</th>
{% for date in date_range %}
<th>{{ date.strftime('%a, %b %d') }}</th>
{% endfor %}
<th>Total Hours</th>
</tr>
</thead>
<tbody id="table-body">
<!-- Team member data will be added dynamically -->
</tbody>
</table>
</div>
<div id="no-data" style="display: none;">
<p>No time entries found for the selected date range.</p>
</div>
<div id="error-message" style="display: none;" class="error-message">
<!-- Error messages will be displayed here -->
</div>
</div>
<div class="team-hours-details" id="member-details" style="display: none;">
<h3>Detailed Entries for <span id="selected-member"></span></h3>
<table class="time-history">
<thead>
<tr>
<th>Date</th>
<th>Arrival</th>
<th>Departure</th>
<th>Work Duration</th>
<th>Break Duration</th>
</tr>
</thead>
<tbody id="details-body">
<!-- Entry details will be added dynamically -->
</tbody>
</table>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Load team hours data when the page loads
loadTeamHoursData();
// Handle date filter form submission
document.getElementById('date-range-form').addEventListener('submit', function(e) {
e.preventDefault();
loadTeamHoursData();
});
function loadTeamHoursData() {
// Show loading indicator
document.getElementById('loading').style.display = 'block';
document.getElementById('team-hours-table').style.display = 'none';
document.getElementById('team-info').style.display = 'none';
document.getElementById('no-data').style.display = 'none';
document.getElementById('error-message').style.display = 'none';
document.getElementById('member-details').style.display = 'none';
// Get filter values
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
const includeSelf = document.getElementById('include-self').checked;
// Build API URL with query parameters
const apiUrl = `/api/team/hours_data?start_date=${startDate}&end_date=${endDate}&include_self=${includeSelf}`;
// Fetch data from API
fetch(apiUrl)
.then(response => {
if (!response.ok) {
return response.json().then(data => {
throw new Error(data.message || 'Failed to load team hours data');
});
}
return response.json();
})
.then(data => {
if (data.success) {
displayTeamData(data);
} else {
showError(data.message || 'Failed to load team hours data.');
}
})
.catch(error => {
console.error('Error fetching team hours data:', error);
showError(error.message || 'An error occurred while loading the team hours data.');
});
}
function displayTeamData(data) {
// Populate team info
document.getElementById('team-name').textContent = data.team.name;
document.getElementById('team-description').textContent = data.team.description || '';
document.getElementById('team-info').style.display = 'block';
// Populate team hours table
const tableHeader = document.getElementById('table-header').querySelector('tr');
tableHeader.innerHTML = '<th>Team Member</th>';
data.date_range.forEach(dateStr => {
const th = document.createElement('th');
th.textContent = new Date(dateStr).toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' });
tableHeader.appendChild(th);
});
const totalHoursTh = document.createElement('th');
totalHoursTh.textContent = 'Total Hours';
tableHeader.appendChild(totalHoursTh);
const tableBody = document.getElementById('table-body');
tableBody.innerHTML = '';
data.team_data.forEach(memberData => {
const row = document.createElement('tr');
// Add username cell
const usernameCell = document.createElement('td');
usernameCell.textContent = memberData.user.username;
row.appendChild(usernameCell);
// Add daily hours cells
data.date_range.forEach(dateStr => {
const cell = document.createElement('td');
cell.textContent = `${memberData.daily_hours[dateStr] || 0}h`;
row.appendChild(cell);
});
// Add total hours cell
const totalCell = document.createElement('td');
totalCell.innerHTML = `<strong>${memberData.total_hours}h</strong>`;
row.appendChild(totalCell);
tableBody.appendChild(row);
});
// Populate detailed entries
document.getElementById('team-hours-table').style.display = 'block';
document.getElementById('export-buttons').style.display = 'block';
document.getElementById('loading').style.display = 'none';
}
function showError(message) {
document.getElementById('error-message').textContent = message;
document.getElementById('error-message').style.display = 'block';
document.getElementById('loading').style.display = 'none';
}
});
// Export function (global scope)
function exportTeamHours(format) {
console.log('Export function called with format:', format);
try {
// Get current filter values
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
const includeSelf = document.getElementById('include-self').checked;
console.log('Filter values:', { startDate, endDate, includeSelf });
// Validate required fields
if (!startDate || !endDate) {
alert('Please select both start and end dates before exporting.');
return;
}
// Build export URL with query parameters
const exportUrl = `/download_team_hours_export?format=${format}&start_date=${startDate}&end_date=${endDate}&include_self=${includeSelf}`;
console.log('Export URL:', exportUrl);
// Show loading indicator
const exportButtons = document.getElementById('export-buttons');
const originalHTML = exportButtons.innerHTML;
exportButtons.innerHTML = '<h4>Generating export...</h4><p>Please wait...</p>';
// Trigger download
window.location.href = exportUrl;
// Restore buttons after a short delay
setTimeout(() => {
exportButtons.innerHTML = originalHTML;
}, 2000);
} catch (error) {
console.error('Error in exportTeamHours:', error);
alert('An error occurred while trying to export. Please try again.');
}
}
</script>
{% endblock %}