Files
TimeTrack/templates/admin_company.html

699 lines
24 KiB
HTML

{% extends "layout.html" %}
{% block content %}
<div class="company-admin-container">
<!-- Header Section -->
<div class="page-header">
<div class="header-content">
<div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-building"></i></span>
Company Management
</h1>
<p class="page-subtitle">Configure your company settings and policies</p>
</div>
<div class="header-actions">
<a href="{{ url_for('setup_company') }}" class="btn btn-primary">
<span class="icon">+</span>
Create New Company
</a>
</div>
</div>
</div>
<!-- Company Statistics -->
<div class="stats-section">
<div class="stat-card">
<div class="stat-value">{{ stats.total_users }}</div>
<div class="stat-label">Total Users</div>
<a href="{{ url_for('companies.company_users') }}" class="stat-link">View all <i class="ti ti-arrow-right"></i></a>
</div>
<div class="stat-card">
<div class="stat-value">{{ stats.total_teams }}</div>
<div class="stat-label">Teams</div>
<a href="{{ url_for('organization.admin_organization') }}" class="stat-link">Manage <i class="ti ti-arrow-right"></i></a>
</div>
<div class="stat-card">
<div class="stat-value">{{ stats.total_projects }}</div>
<div class="stat-label">Total Projects</div>
<a href="{{ url_for('projects.admin_projects') }}" class="stat-link">View all <i class="ti ti-arrow-right"></i></a>
</div>
<div class="stat-card">
<div class="stat-value">{{ stats.active_projects }}</div>
<div class="stat-label">Active Projects</div>
<a href="{{ url_for('projects.admin_projects') }}" class="stat-link">Manage <i class="ti ti-arrow-right"></i></a>
</div>
</div>
<!-- Main Content Grid -->
<div class="content-grid">
<!-- Left Column -->
<div class="content-column">
<!-- Company Information Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-info-circle"></i></span>
Company Information
</h2>
</div>
<div class="card-body">
<form method="POST" class="modern-form">
<input type="hidden" name="action" value="update_company_details">
<div class="form-group">
<label for="name" class="form-label">Company Name</label>
<input type="text" id="name" name="name" class="form-control"
value="{{ company.name }}" required>
<span class="form-hint">The official name of your company</span>
</div>
<div class="form-row">
<div class="form-group">
<label for="max_users" class="form-label">Maximum Users</label>
<input type="number" id="max_users" name="max_users" class="form-control"
value="{{ company.max_users or '' }}" min="1" placeholder="Unlimited">
<span class="form-hint">Leave empty for unlimited</span>
</div>
<div class="form-group">
<label class="form-label">Status</label>
<div class="toggle-container">
<label class="toggle-switch">
<input type="checkbox" id="is_active" name="is_active"
{{ 'checked' if company.is_active else '' }}>
<span class="toggle-slider"></span>
</label>
<span class="toggle-label">Company is {{ 'active' if company.is_active else 'inactive' }}</span>
</div>
</div>
</div>
<div class="form-group">
<label for="description" class="form-label">Description</label>
<textarea id="description" name="description" class="form-control"
rows="3" placeholder="Brief description of your company...">{{ company.description or '' }}</textarea>
<span class="form-hint">Optional: Describe your company's mission or purpose</span>
</div>
<div class="info-panel">
<div class="info-item">
<span class="info-icon"><i class="ti ti-key"></i></span>
<div class="info-content">
<label class="info-label">Company Code</label>
<div class="code-display">
<input type="text" value="{{ company.slug }}" readonly id="companyCode" class="code-input">
<button type="button" class="btn btn-copy" onclick="copyCompanyCode()">
<span id="copyIcon"><i class="ti ti-clipboard"></i></span>
<span id="copyText">Copy</span>
</button>
</div>
<span class="info-hint">Legacy: Use email invitations instead</span>
</div>
</div>
<div class="info-item">
<span class="info-icon"><i class="ti ti-calendar"></i></span>
<div class="info-content">
<label class="info-label">Created</label>
<span class="info-value">{{ company.created_at|format_datetime }}</span>
</div>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<span class="icon"><i class="ti ti-check"></i></span>
Save Company Details
</button>
</div>
</form>
</div>
</div>
<!-- Quick Actions Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-bolt"></i></span>
Quick Actions
</h2>
</div>
<div class="card-body">
<div class="action-grid">
<a href="{{ url_for('organization.admin_organization') }}" class="action-item">
<div class="action-icon"><i class="ti ti-sitemap"></i></div>
<div class="action-content">
<h3>Manage Organization</h3>
<p>Users, teams & structure</p>
</div>
</a>
<a href="{{ url_for('projects.admin_projects') }}" class="action-item">
<div class="action-icon"><i class="ti ti-folder"></i></div>
<div class="action-content">
<h3>Manage Projects</h3>
<p>Time tracking projects</p>
</div>
</a>
<a href="{{ url_for('invitations.send_invitation') }}" class="action-item">
<div class="action-icon"><i class="ti ti-mail"></i></div>
<div class="action-content">
<h3>Send Invitation</h3>
<p>Invite team members</p>
</div>
</a>
</div>
</div>
</div>
</div>
<!-- Right Column -->
<div class="content-column">
<!-- Work Policies Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-clipboard-list"></i></span>
Work Policies
</h2>
</div>
<div class="card-body">
<!-- Regional Presets -->
<div class="preset-section">
<h3 class="section-title">Regional Preset</h3>
<form method="POST" action="{{ url_for('companies.admin_company') }}" class="preset-form">
<input type="hidden" name="action" value="update_work_policies">
<input type="hidden" name="apply_preset" value="true">
<select name="region_preset" class="form-control form-select" onchange="this.form.submit()">
<option value="">Select a regional preset...</option>
{% for preset in regional_presets %}
<option value="{{ preset.code }}" {% if work_config.work_region.value == preset.code %}selected{% endif %}>
{{ preset.name }} - {{ preset.description }}
</option>
{% endfor %}
</select>
</form>
</div>
<!-- Current Configuration -->
<div class="config-section">
<h3 class="section-title">
Current Configuration
<span class="config-badge">{{ work_config.work_region.value if work_config.work_region else 'Custom' }}</span>
</h3>
<form method="POST" action="{{ url_for('companies.admin_company') }}" class="modern-form">
<input type="hidden" name="action" value="update_work_policies">
<div class="form-grid">
<div class="form-group">
<label for="standard_hours_per_day" class="form-label">Hours per Day</label>
<input type="number" id="standard_hours_per_day" name="standard_hours_per_day"
class="form-control" value="{{ work_config.standard_hours_per_day }}"
step="0.5" min="1" max="24" required>
</div>
<div class="form-group">
<label for="standard_hours_per_week" class="form-label">Hours per Week</label>
<input type="number" id="standard_hours_per_week" name="standard_hours_per_week"
class="form-control" value="{{ work_config.standard_hours_per_week }}"
step="0.5" min="1" max="168" required>
</div>
<div class="form-group">
<label for="break_duration_minutes" class="form-label">Break Duration (min)</label>
<input type="number" id="break_duration_minutes" name="break_duration_minutes"
class="form-control" value="{{ work_config.break_duration_minutes }}"
min="0" max="120" required>
</div>
<div class="form-group">
<label for="break_after_hours" class="form-label">Break After (hours)</label>
<input type="number" id="break_after_hours" name="break_after_hours"
class="form-control" value="{{ work_config.break_after_hours }}"
step="0.5" min="0" max="24" required>
</div>
<div class="form-group">
<label for="overtime_rate" class="form-label">Overtime Rate</label>
<input type="number" id="overtime_rate" name="overtime_rate"
class="form-control" value="{{ work_config.overtime_rate }}"
step="0.1" min="1" max="3" required>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<span class="icon"><i class="ti ti-check"></i></span>
Update Policies
</button>
</div>
</form>
</div>
</div>
</div>
<!-- User Registration Settings Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-user"></i></span>
User Registration
</h2>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('companies.admin_company') }}" class="modern-form">
<input type="hidden" name="action" value="update_system_settings">
<div class="settings-list">
<div class="setting-item">
<div class="setting-toggle">
<label class="toggle-switch">
<input type="checkbox" id="registration_enabled" name="registration_enabled"
{% if settings.registration_enabled %}checked{% endif %}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-content">
<h4 class="setting-title">Enable User Registration</h4>
<p class="setting-description">Allow new users to register accounts using the company code</p>
</div>
</div>
<div class="setting-item">
<div class="setting-toggle">
<label class="toggle-switch">
<input type="checkbox" id="email_verification_required" name="email_verification_required"
{% if settings.email_verification_required %}checked{% endif %}>
<span class="toggle-slider"></span>
</label>
</div>
<div class="setting-content">
<h4 class="setting-title">Require Email Verification</h4>
<p class="setting-description">New users must verify their email address before accessing the system</p>
</div>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<span class="icon"><i class="ti ti-check"></i></span>
Update Settings
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<style>
/* Container */
/* Company Admin specific styles */
.stat-link {
position: absolute;
bottom: 0.75rem;
right: 1rem;
font-size: 0.875rem;
color: #667eea;
text-decoration: none;
opacity: 0.7;
transition: opacity 0.2s;
}
.stat-link:hover {
opacity: 1;
text-decoration: underline;
}
/* Company-specific content grid - already defined in common styles */
/* Company-specific card styles */
.card-title .icon {
font-size: 1.5rem;
}
/* Company-specific form styles */
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.form-control {
padding: 0.75rem 1rem;
border: 2px solid #e5e7eb;
border-radius: 8px;
font-size: 1rem;
transition: all 0.2s ease;
background-color: #f9fafb;
}
.form-control:focus {
outline: none;
border-color: #667eea;
background-color: white;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-select {
cursor: pointer;
}
.form-hint {
font-size: 0.875rem;
color: #6b7280;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 0.5rem;
}
/* Toggle Switch */
.toggle-container {
display: flex;
align-items: center;
gap: 1rem;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 60px;
height: 32px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #cbd5e1;
transition: .4s;
border-radius: 34px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 24px;
width: 24px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: #667eea;
}
input:checked + .toggle-slider:before {
transform: translateX(28px);
}
.toggle-label {
font-weight: 500;
color: #374151;
}
/* Info Panel */
.info-panel {
background: #f3f4f6;
border-radius: 8px;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.info-item {
display: flex;
gap: 1rem;
align-items: flex-start;
}
.info-icon {
font-size: 1.5rem;
flex-shrink: 0;
}
.info-content {
flex: 1;
}
.info-label {
font-weight: 600;
color: #374151;
display: block;
margin-bottom: 0.5rem;
}
.info-value {
color: #667eea;
font-weight: 500;
}
.info-hint {
font-size: 0.875rem;
color: #6b7280;
margin-top: 0.25rem;
}
.code-display {
display: flex;
gap: 0.5rem;
align-items: center;
}
.code-input {
font-family: 'Monaco', 'Courier New', monospace;
font-weight: 600;
color: #667eea;
background: white;
border: 2px solid #e5e7eb;
padding: 0.5rem 1rem;
border-radius: 6px;
flex: 1;
max-width: 300px;
}
/* Company-specific button styles */
.btn-copy {
background: white;
color: #6b7280;
border: 2px solid #e5e7eb;
padding: 0.5rem 1rem;
}
.btn-copy:hover {
background: #f3f4f6;
border-color: #d1d5db;
}
.btn-copy.success {
background: #d1fae5;
color: #059669;
border-color: #10b981;
}
/* Company-specific action grid - already defined in common styles */
.action-item {
display: flex;
gap: 1rem;
padding: 1.25rem;
background: #f8f9fa;
border-radius: 8px;
text-decoration: none;
transition: all 0.2s ease;
border: 2px solid transparent;
}
.action-item:hover {
background: white;
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
}
.action-icon {
font-size: 2rem;
flex-shrink: 0;
}
.action-icon i {
font-size: 2rem;
color: #667eea;
}
.action-content h3 {
font-size: 1.05rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 0.25rem 0;
}
.action-content p {
font-size: 0.875rem;
color: #6b7280;
margin: 0;
}
/* Work Policies */
.preset-section,
.config-section {
margin-bottom: 2rem;
}
.section-title {
font-size: 1.1rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.config-badge {
background: #ede9fe;
color: #5b21b6;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
}
/* Settings List */
.settings-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.setting-item {
display: flex;
gap: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
align-items: flex-start;
}
.setting-toggle {
flex-shrink: 0;
}
.setting-content {
flex: 1;
}
.setting-title {
font-size: 1.05rem;
font-weight: 600;
color: #1f2937;
margin: 0 0 0.25rem 0;
}
.setting-description {
font-size: 0.875rem;
color: #6b7280;
margin: 0;
}
/* Company-specific responsive styles */
@media (max-width: 768px) {
.company-admin-container {
padding: 1rem;
}
.form-row,
.form-grid {
grid-template-columns: 1fr;
}
.code-input {
max-width: 100%;
}
}
/* Animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: slideIn 0.3s ease-out;
animation-fill-mode: both;
}
.card:nth-child(1) { animation-delay: 0.1s; }
.card:nth-child(2) { animation-delay: 0.2s; }
.card:nth-child(3) { animation-delay: 0.3s; }
</style>
<script>
function copyCompanyCode() {
const codeInput = document.getElementById('companyCode');
codeInput.select();
codeInput.setSelectionRange(0, 99999);
try {
document.execCommand('copy');
// Show feedback
const button = event.currentTarget;
const copyIcon = document.getElementById('copyIcon');
const copyText = document.getElementById('copyText');
// Store original values
const originalIcon = copyIcon.innerHTML;
const originalText = copyText.textContent;
// Update to success state
copyIcon.innerHTML = '<i class="ti ti-check"></i>';
copyText.textContent = 'Copied!';
button.classList.add('success');
// Reset after 2 seconds
setTimeout(() => {
copyIcon.innerHTML = originalIcon;
copyText.textContent = originalText;
button.classList.remove('success');
}, 2000);
} catch (err) {
alert('Failed to copy code. Please select and copy manually.');
}
}
</script>
{% endblock %}