mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-01-19 10:50:11 -06:00
Timer/Editing - Add/edit time-entry UI and flows in templates (`templates/timer/*`) - Extend timer and API routes (`app/routes/timer.py`, `app/routes/api.py`) - Update mobile interactions (`app/static/mobile.js`) Invoices/PDF - Improve invoice model and route handling (`app/models/invoice.py`, `app/routes/invoices.py`) - Enhance PDF generation and fallback logic (`app/utils/pdf_generator*.py`) - Adjust invoice view layout (`templates/invoices/view.html`) Docker/Startup - Refine Docker build and startup paths (`Dockerfile`) - Improve init/entrypoint scripts (`docker/init-database-*.py`, new `docker/entrypoint*.sh`, `docker/entrypoint.py`) - General startup robustness and permissions fixes Docs/UI - Refresh README and Docker docs (setup, troubleshooting, structure) - Minor UI/help updates (`templates/main/help.html`, `templates/projects/create.html`) - Remove obsolete asset (`assets/screenshots/Task_Management.png`) - Add repo hygiene updates (e.g., `.gitattributes`)
462 lines
20 KiB
HTML
462 lines
20 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Create Project - {{ app_name }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-4">
|
|
<!-- Header -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card mobile-card">
|
|
<div class="card-body py-4">
|
|
<div class="d-flex align-items-center">
|
|
<div class="bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-3" style="width: 48px; height: 48px;">
|
|
<i class="fas fa-project-diagram text-primary fa-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h1 class="h2 mb-1">Create New Project</h1>
|
|
<p class="text-muted mb-0">Set up a new project to organize your work and track time effectively</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Create Project Form -->
|
|
<div class="row">
|
|
<div class="col-lg-8">
|
|
<div class="card mobile-card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-edit me-2 text-primary"></i>Project Information
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="{{ url_for('projects.create_project') }}" novalidate id="createProjectForm">
|
|
<!-- Project Name and Client -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-8">
|
|
<div class="mb-3">
|
|
<label for="name" class="form-label fw-semibold">
|
|
<i class="fas fa-tag me-2 text-primary"></i>Project Name <span class="text-danger">*</span>
|
|
</label>
|
|
<input type="text" class="form-control form-control-lg" id="name" name="name" required
|
|
value="{{ request.form.get('name','') }}" placeholder="Enter a descriptive project name">
|
|
<small class="form-text text-muted">Choose a clear, descriptive name that explains the project scope</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="mb-3">
|
|
<label for="client_id" class="form-label fw-semibold">
|
|
<i class="fas fa-user me-2 text-info"></i>Client <span class="text-danger">*</span>
|
|
</label>
|
|
<select class="form-select form-select-lg" id="client_id" name="client_id" required>
|
|
<option value="">Select a client...</option>
|
|
{% for client in clients %}
|
|
<option value="{{ client.id }}"
|
|
{% if request.form.get('client_id') == client.id|string %}selected{% endif %}
|
|
data-default-rate="{{ client.default_hourly_rate or '' }}">
|
|
{{ client.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
<div class="form-text">
|
|
<a href="{{ url_for('clients.create_client') }}" class="text-decoration-none">
|
|
<i class="fas fa-plus"></i> Create new client
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Description -->
|
|
<div class="mb-4">
|
|
<label for="description" class="form-label fw-semibold">
|
|
<i class="fas fa-align-left me-2 text-primary"></i>Description
|
|
</label>
|
|
<textarea class="form-control" id="description" name="description" rows="4"
|
|
placeholder="Provide detailed information about the project, objectives, and deliverables...">{{ request.form.get('description','') }}</textarea>
|
|
<small class="form-text text-muted">Optional: Add context, objectives, or specific requirements for the project</small>
|
|
</div>
|
|
|
|
<!-- Billing Settings -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="billable" name="billable" {% if request.form.get('billable') %}checked{% endif %}>
|
|
<label class="form-check-label fw-semibold" for="billable">
|
|
<i class="fas fa-dollar-sign me-2 text-success"></i>Billable Project
|
|
</label>
|
|
<small class="form-text text-muted d-block">Enable billing for this project</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="mb-3">
|
|
<label for="hourly_rate" class="form-label fw-semibold">
|
|
<i class="fas fa-clock me-2 text-warning"></i>Hourly Rate
|
|
</label>
|
|
<input type="number" step="0.01" min="0" class="form-control" id="hourly_rate" name="hourly_rate"
|
|
value="{{ request.form.get('hourly_rate','') }}" placeholder="e.g. 75.00">
|
|
<small class="form-text text-muted">Leave empty for non-billable projects</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing Reference -->
|
|
<div class="mb-4">
|
|
<label for="billing_ref" class="form-label fw-semibold">
|
|
<i class="fas fa-receipt me-2 text-secondary"></i>Billing Reference
|
|
</label>
|
|
<input type="text" class="form-control" id="billing_ref" name="billing_ref"
|
|
value="{{ request.form.get('billing_ref','') }}" placeholder="PO number, contract reference, etc.">
|
|
<small class="form-text text-muted">Optional: Add a reference number or identifier for billing purposes</small>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="d-flex gap-3 pt-3 border-top">
|
|
<button type="submit" class="btn btn-primary btn-lg">
|
|
<i class="fas fa-save me-2"></i>Create Project
|
|
</button>
|
|
<a href="{{ url_for('projects.list_projects') }}" class="btn btn-outline-secondary btn-lg">
|
|
<i class="fas fa-times me-2"></i>Cancel
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar Help -->
|
|
<div class="col-lg-4">
|
|
<!-- Project Creation Tips -->
|
|
<div class="card mobile-card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-lightbulb me-2 text-warning"></i>Project Creation Tips
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="tip-item mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-check text-primary fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Clear Naming</small>
|
|
<small class="text-muted">Use descriptive names that clearly indicate the project's purpose</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tip-item mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-dollar-sign text-success fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Billing Setup</small>
|
|
<small class="text-muted">Set appropriate hourly rates based on project complexity and client budget</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tip-item mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-info-circle text-info fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Detailed Description</small>
|
|
<small class="text-muted">Include project objectives, deliverables, and key requirements</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tip-item">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-warning bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-user text-warning fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Client Selection</small>
|
|
<small class="text-muted">Choose the right client to ensure proper project organization</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing Guide -->
|
|
<div class="card mobile-card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-info-circle me-2 text-info"></i>Billing Guide
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="billing-guide-item mb-3">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<span class="billing-badge billing-billable me-2">Billable</span>
|
|
<small class="text-muted">Track time and bill client</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="billing-guide-item mb-3">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<span class="billing-badge billing-non-billable me-2">Non-Billable</span>
|
|
<small class="text-muted">Track time without billing</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="billing-guide-item">
|
|
<div class="d-flex align-items-center mb-2">
|
|
<span class="billing-badge billing-rate me-2">Rate Setting</span>
|
|
<small class="text-muted">Set appropriate hourly rates</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Project Management -->
|
|
<div class="card mobile-card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-tasks me-2 text-secondary"></i>Project Management
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="management-item mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-primary bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-clock text-primary fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Time Tracking</small>
|
|
<small class="text-muted">Log time entries for accurate project tracking</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="management-item mb-3">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-success bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-list text-success fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Task Creation</small>
|
|
<small class="text-muted">Break down projects into manageable tasks</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="management-item">
|
|
<div class="d-flex align-items-start">
|
|
<div class="bg-info bg-opacity-10 rounded-circle d-flex align-items-center justify-content-center me-2" style="width: 24px; height: 24px;">
|
|
<i class="fas fa-chart-bar text-info fa-xs"></i>
|
|
</div>
|
|
<div>
|
|
<small class="fw-semibold d-block">Progress Monitoring</small>
|
|
<small class="text-muted">Track project progress and time allocation</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
/* Billing Badges */
|
|
.billing-badge {
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 20px;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.billing-billable {
|
|
background-color: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.billing-non-billable {
|
|
background-color: #e2e8f0;
|
|
color: #475569;
|
|
}
|
|
|
|
.billing-rate {
|
|
background-color: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
/* Tip Items */
|
|
.tip-item {
|
|
padding: 0.75rem;
|
|
border-radius: 8px;
|
|
background-color: #f8fafc;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.tip-item:hover {
|
|
background-color: #f1f5f9;
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
/* Billing and Management Guide Items */
|
|
.billing-guide-item,
|
|
.management-item {
|
|
padding: 0.5rem;
|
|
border-radius: 6px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.billing-guide-item:hover,
|
|
.management-item:hover {
|
|
background-color: #f8fafc;
|
|
}
|
|
|
|
/* Form Styling */
|
|
.form-control:focus,
|
|
.form-select:focus {
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 0.2rem rgba(59, 130, 246, 0.25);
|
|
}
|
|
|
|
.form-control-lg {
|
|
min-height: 56px;
|
|
}
|
|
|
|
.form-select-lg {
|
|
min-height: 56px;
|
|
}
|
|
|
|
/* Mobile Optimizations */
|
|
@media (max-width: 768px) {
|
|
.card-header {
|
|
padding: 1rem 1rem 0.75rem 1rem;
|
|
}
|
|
|
|
.card-body {
|
|
padding: 0.75rem 1rem;
|
|
}
|
|
|
|
.tip-item:hover {
|
|
transform: none;
|
|
}
|
|
|
|
.billing-guide-item:hover,
|
|
.management-item:hover {
|
|
background-color: transparent;
|
|
}
|
|
}
|
|
|
|
/* Hover Effects */
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
|
}
|
|
|
|
.tip-item:hover {
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
const clientSelect = document.getElementById('client_id');
|
|
const hourlyRateInput = document.getElementById('hourly_rate');
|
|
const billableCheckbox = document.getElementById('billable');
|
|
const form = document.getElementById('createProjectForm');
|
|
const nameInput = document.getElementById('name');
|
|
|
|
// Auto-fill hourly rate from client default
|
|
clientSelect.addEventListener('change', function() {
|
|
const selectedOption = this.options[this.selectedIndex];
|
|
const defaultRate = selectedOption.getAttribute('data-default-rate');
|
|
|
|
if (defaultRate && !hourlyRateInput.value) {
|
|
hourlyRateInput.value = defaultRate;
|
|
}
|
|
});
|
|
|
|
// Toggle hourly rate field based on billable checkbox
|
|
billableCheckbox.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
hourlyRateInput.removeAttribute('disabled');
|
|
hourlyRateInput.classList.remove('text-muted');
|
|
} else {
|
|
hourlyRateInput.setAttribute('disabled', 'disabled');
|
|
hourlyRateInput.classList.add('text-muted');
|
|
hourlyRateInput.value = '';
|
|
}
|
|
});
|
|
|
|
// Form validation and enhancement
|
|
form.addEventListener('submit', function(e) {
|
|
const name = nameInput.value.trim();
|
|
const clientId = clientSelect.value;
|
|
|
|
if (!name || !clientId) {
|
|
e.preventDefault();
|
|
if (!name) {
|
|
nameInput.focus();
|
|
nameInput.classList.add('is-invalid');
|
|
}
|
|
if (!clientId) {
|
|
clientSelect.focus();
|
|
clientSelect.classList.add('is-invalid');
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
const originalText = submitBtn.innerHTML;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Creating...';
|
|
submitBtn.disabled = true;
|
|
|
|
// Re-enable after a delay (in case of validation errors)
|
|
setTimeout(() => {
|
|
submitBtn.innerHTML = originalText;
|
|
submitBtn.disabled = false;
|
|
}, 5000);
|
|
});
|
|
|
|
// Real-time validation feedback
|
|
nameInput.addEventListener('input', function() {
|
|
if (this.value.trim()) {
|
|
this.classList.remove('is-invalid');
|
|
this.classList.add('is-valid');
|
|
} else {
|
|
this.classList.remove('is-valid');
|
|
}
|
|
});
|
|
|
|
clientSelect.addEventListener('change', function() {
|
|
if (this.value) {
|
|
this.classList.remove('is-invalid');
|
|
this.classList.add('is-valid');
|
|
} else {
|
|
this.classList.remove('is-valid');
|
|
}
|
|
});
|
|
|
|
// Initialize form state
|
|
if (billableCheckbox.checked) {
|
|
hourlyRateInput.removeAttribute('disabled');
|
|
} else {
|
|
hourlyRateInput.setAttribute('disabled', 'disabled');
|
|
hourlyRateInput.classList.add('text-muted');
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|
|
|
|
|