Streamline System Admin styles.

This commit is contained in:
2025-07-09 07:50:59 +02:00
parent f476858df1
commit 17190d08d6
11 changed files with 5169 additions and 2118 deletions

View File

@@ -1,164 +1,213 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="announcement-form-container">
<div class="header-section"> <!-- Header Section -->
<h1>{{ "Edit" if announcement else "Create" }} Announcement</h1> <div class="page-header">
<p class="subtitle">{{ "Update" if announcement else "Create new" }} system announcement for users</p> <div class="header-content">
<a href="{{ url_for('announcements.index') }}" class="btn btn-secondary"> <div class="header-left">
<i class="ti ti-arrow-left"></i> Back to Announcements <h1 class="page-title">
</a> <span class="page-icon"><i class="ti ti-speakerphone"></i></span>
{{ "Edit" if announcement else "Create" }} Announcement
</h1>
<p class="page-subtitle">{{ "Update" if announcement else "Create new" }} system announcement for users</p>
</div>
<div class="header-actions">
<a href="{{ url_for('announcements.index') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Announcements
</a>
</div>
</div>
</div> </div>
<div class="form-section"> <!-- Main Form -->
<form method="POST" class="announcement-form"> <form method="POST" class="announcement-form">
<div class="form-group"> <!-- Basic Information -->
<label for="title">Title</label> <div class="card">
<input type="text" <div class="card-header">
id="title" <h2 class="card-title">
name="title" <span class="icon"><i class="ti ti-forms"></i></span>
value="{{ announcement.title if announcement else '' }}" Basic Information
required </h2>
maxlength="200"
class="form-control">
</div> </div>
<div class="card-body">
<div class="form-group">
<label for="content">Content</label>
<textarea id="content"
name="content"
required
rows="6"
class="form-control">{{ announcement.content if announcement else '' }}</textarea>
<small class="form-text">You can use HTML formatting in the content.</small>
</div>
<div class="form-row">
<div class="form-group"> <div class="form-group">
<label for="announcement_type">Type</label> <label for="title">Title</label>
<select id="announcement_type" name="announcement_type" class="form-control"> <input type="text"
<option value="info" {{ 'selected' if announcement and announcement.announcement_type == 'info' else '' }}>Info</option> id="title"
<option value="warning" {{ 'selected' if announcement and announcement.announcement_type == 'warning' else '' }}>Warning</option> name="title"
<option value="success" {{ 'selected' if announcement and announcement.announcement_type == 'success' else '' }}>Success</option> value="{{ announcement.title if announcement else '' }}"
<option value="danger" {{ 'selected' if announcement and announcement.announcement_type == 'danger' else '' }}>Danger</option> required
</select> maxlength="200"
class="form-control"
placeholder="Enter announcement title">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="checkbox-container"> <label for="content">Content</label>
<input type="checkbox" <textarea id="content"
name="is_urgent" name="content"
{{ 'checked' if announcement and announcement.is_urgent else '' }}> required
<span class="checkmark"></span> rows="6"
Mark as Urgent class="form-control"
</label> placeholder="Enter announcement content">{{ announcement.content if announcement else '' }}</textarea>
<small class="form-text">You can use HTML formatting in the content.</small>
</div> </div>
<div class="form-group"> <div class="form-row">
<label class="checkbox-container"> <div class="form-group">
<input type="checkbox" <label for="announcement_type">Type</label>
name="is_active" <select id="announcement_type" name="announcement_type" class="form-control">
{{ 'checked' if not announcement or announcement.is_active else '' }}> <option value="info" {{ 'selected' if announcement and announcement.announcement_type == 'info' else '' }}>
<span class="checkmark"></span> <i class="ti ti-info-circle"></i> Info
Active </option>
</label> <option value="warning" {{ 'selected' if announcement and announcement.announcement_type == 'warning' else '' }}>
</div> <i class="ti ti-alert-triangle"></i> Warning
</div> </option>
<option value="success" {{ 'selected' if announcement and announcement.announcement_type == 'success' else '' }}>
<i class="ti ti-circle-check"></i> Success
</option>
<option value="danger" {{ 'selected' if announcement and announcement.announcement_type == 'danger' else '' }}>
<i class="ti ti-alert-circle"></i> Danger
</option>
</select>
</div>
<div class="form-section"> <div class="form-group">
<h3>Scheduling</h3> <label>Options</label>
<div class="form-row"> <div class="checkbox-group">
<div class="form-group"> <label class="toggle-label">
<label for="start_date">Start Date/Time (Optional)</label> <input type="checkbox"
<input type="datetime-local" name="is_urgent"
id="start_date" {{ 'checked' if announcement and announcement.is_urgent else '' }}>
name="start_date" <span class="toggle-slider"></span>
value="{{ announcement.start_date.strftime('%Y-%m-%dT%H:%M') if announcement and announcement.start_date else '' }}" <span class="toggle-text">Mark as Urgent</span>
class="form-control"> </label>
<small class="form-text">Leave empty to show immediately</small>
</div> <label class="toggle-label">
<input type="checkbox"
<div class="form-group"> name="is_active"
<label for="end_date">End Date/Time (Optional)</label> {{ 'checked' if not announcement or announcement.is_active else '' }}>
<input type="datetime-local" <span class="toggle-slider"></span>
id="end_date" <span class="toggle-text">Active</span>
name="end_date" </label>
value="{{ announcement.end_date.strftime('%Y-%m-%dT%H:%M') if announcement and announcement.end_date else '' }}" </div>
class="form-control"> </div>
<small class="form-text">Leave empty for no expiry</small>
</div> </div>
</div> </div>
</div> </div>
<div class="form-section"> <!-- Scheduling -->
<h3>Targeting</h3> <div class="card">
<div class="form-row"> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-calendar-event"></i></span>
Scheduling
</h2>
</div>
<div class="card-body">
<div class="form-row">
<div class="form-group">
<label for="start_date">Start Date/Time</label>
<input type="datetime-local"
id="start_date"
name="start_date"
value="{{ announcement.start_date.strftime('%Y-%m-%dT%H:%M') if announcement and announcement.start_date else '' }}"
class="form-control">
<small class="form-text">Leave empty to show immediately</small>
</div>
<div class="form-group">
<label for="end_date">End Date/Time</label>
<input type="datetime-local"
id="end_date"
name="end_date"
value="{{ announcement.end_date.strftime('%Y-%m-%dT%H:%M') if announcement and announcement.end_date else '' }}"
class="form-control">
<small class="form-text">Leave empty for no expiry</small>
</div>
</div>
</div>
</div>
<!-- Targeting -->
<div class="card">
<div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-target"></i></span>
Targeting
</h2>
</div>
<div class="card-body">
<div class="form-group"> <div class="form-group">
<label class="checkbox-container"> <label class="toggle-label main-toggle">
<input type="checkbox" <input type="checkbox"
name="target_all_users" name="target_all_users"
id="target_all_users" id="target_all_users"
{{ 'checked' if not announcement or announcement.target_all_users else '' }} {{ 'checked' if not announcement or announcement.target_all_users else '' }}
onchange="toggleTargeting()"> onchange="toggleTargeting()">
<span class="checkmark"></span> <span class="toggle-slider"></span>
Target All Users <span class="toggle-text">Target All Users</span>
</label> </label>
<small class="form-text">When enabled, announcement will be shown to all users regardless of role or company</small>
</div> </div>
</div>
<div id="targeting_options" style="display: {{ 'none' if not announcement or announcement.target_all_users else 'block' }};"> <div id="targeting_options" style="display: {{ 'none' if not announcement or announcement.target_all_users else 'block' }};">
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>Target Roles</label> <label><i class="ti ti-user-check"></i> Target Roles</label>
<div class="checkbox-list"> <div class="checkbox-list">
{% set selected_roles = [] %} {% set selected_roles = [] %}
{% if announcement and announcement.target_roles %} {% if announcement and announcement.target_roles %}
{% set selected_roles = announcement.target_roles|from_json %} {% set selected_roles = announcement.target_roles|from_json %}
{% endif %} {% endif %}
{% for role in roles %} {% for role in roles %}
<label class="checkbox-container"> <label class="checkbox-item">
<input type="checkbox" <input type="checkbox"
name="target_roles" name="target_roles"
value="{{ role }}" value="{{ role }}"
{{ 'checked' if role in selected_roles else '' }}> {{ 'checked' if role in selected_roles else '' }}>
<span class="checkmark"></span> <span class="checkbox-custom"></span>
{{ role }} <span class="checkbox-label">{{ role }}</span>
</label> </label>
{% endfor %} {% endfor %}
</div>
</div> </div>
</div>
<div class="form-group"> <div class="form-group">
<label>Target Companies</label> <label><i class="ti ti-building"></i> Target Companies</label>
<div class="checkbox-list"> <div class="checkbox-list">
{% set selected_companies = [] %} {% set selected_companies = [] %}
{% if announcement and announcement.target_companies %} {% if announcement and announcement.target_companies %}
{% set selected_companies = announcement.target_companies|from_json %} {% set selected_companies = announcement.target_companies|from_json %}
{% endif %} {% endif %}
{% for company in companies %} {% for company in companies %}
<label class="checkbox-container"> <label class="checkbox-item">
<input type="checkbox" <input type="checkbox"
name="target_companies" name="target_companies"
value="{{ company.id }}" value="{{ company.id }}"
{{ 'checked' if company.id in selected_companies else '' }}> {{ 'checked' if company.id in selected_companies else '' }}>
<span class="checkmark"></span> <span class="checkbox-custom"></span>
{{ company.name }} <span class="checkbox-label">{{ company.name }}</span>
</label> </label>
{% endfor %} {% endfor %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="form-actions"> <!-- Form Actions -->
<button type="submit" class="btn btn-primary"> <div class="form-actions">
{{ "Update" if announcement else "Create" }} Announcement <button type="submit" class="btn btn-primary">
</button> <i class="ti ti-device-floppy"></i>
<a href="{{ url_for('announcements.index') }}" class="btn btn-secondary">Cancel</a> {{ "Update" if announcement else "Create" }} Announcement
</div> </button>
</form> <a href="{{ url_for('announcements.index') }}" class="btn btn-secondary">Cancel</a>
</div> </div>
</form>
</div> </div>
<script> <script>
@@ -180,155 +229,334 @@ document.addEventListener('DOMContentLoaded', function() {
</script> </script>
<style> <style>
.header-section { /* Container */
margin-bottom: 2rem; .announcement-form-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
} }
.subtitle { /* Page Header */
color: #6c757d; .page-header {
margin-bottom: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} border-radius: 16px;
.form-section {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 2rem; padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
} }
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
background: #f8f9fa;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
/* Form */
.announcement-form { .announcement-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; gap: 0;
}
.form-section h3 {
margin-top: 0;
margin-bottom: 1rem;
color: #495057;
font-size: 1.2rem;
border-bottom: 1px solid #e9ecef;
padding-bottom: 0.5rem;
} }
.form-group { .form-group {
margin-bottom: 1rem; margin-bottom: 1.5rem;
} }
.form-group label { .form-group label {
display: block; display: block;
font-weight: 600;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 500; color: #374151;
color: #495057;
} }
.form-control { .form-control {
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.625rem 1rem;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 4px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
box-sizing: border-box; transition: all 0.2s ease;
} }
.form-control:focus { .form-control:focus {
outline: none; outline: none;
border-color: #007bff; border-color: #667eea;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
textarea.form-control {
resize: vertical;
min-height: 120px;
} }
.form-text { .form-text {
display: block; display: block;
margin-top: 0.25rem; margin-top: 0.25rem;
color: #6c757d;
font-size: 0.875rem; font-size: 0.875rem;
color: #6b7280;
} }
.form-row { .form-row {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
/* Toggle Switches */
.checkbox-group {
display: flex;
flex-direction: column;
gap: 1rem; gap: 1rem;
} }
.checkbox-container { .toggle-label {
display: block; display: inline-flex;
position: relative; align-items: center;
padding-left: 35px; gap: 0.75rem;
margin-bottom: 12px;
cursor: pointer; cursor: pointer;
font-size: 16px; margin-bottom: 0;
user-select: none; padding: 0;
} }
.checkbox-container input { .toggle-label input[type="checkbox"] {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 25px;
width: 25px;
background-color: #eee;
border-radius: 4px;
}
.checkbox-container:hover input ~ .checkmark {
background-color: #ccc;
}
.checkbox-container input:checked ~ .checkmark {
background-color: #2196F3;
}
.checkmark:after {
content: "";
position: absolute;
display: none; display: none;
} }
.checkbox-container input:checked ~ .checkmark:after { .toggle-slider {
display: block; position: relative;
display: inline-block;
width: 50px;
height: 24px;
background: #e5e7eb;
border-radius: 24px;
transition: background 0.3s;
flex-shrink: 0;
} }
.checkbox-container .checkmark:after { .toggle-slider::before {
left: 9px; content: '';
top: 5px; position: absolute;
width: 5px; width: 20px;
height: 10px; height: 20px;
border: solid white; border-radius: 50%;
border-width: 0 3px 3px 0; background: white;
transform: rotate(45deg); top: 2px;
left: 2px;
transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider {
background: #667eea;
}
.toggle-label input[type="checkbox"]:checked + .toggle-slider::before {
transform: translateX(26px);
}
.toggle-text {
font-weight: 500;
color: #1f2937;
}
.main-toggle {
margin-bottom: 1rem;
}
/* Checkbox Lists */
.checkbox-list { .checkbox-list {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 0.5rem; gap: 0.75rem;
max-height: 200px; max-height: 250px;
overflow-y: auto; overflow-y: auto;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 0.25rem; border-radius: 8px;
padding: 0.75rem; padding: 1rem;
background: #f8f9fa; background: #f8f9fa;
} }
.checkbox-item {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
padding: 0.25rem 0;
}
.checkbox-item input[type="checkbox"] {
display: none;
}
.checkbox-custom {
width: 20px;
height: 20px;
border: 2px solid #e5e7eb;
border-radius: 4px;
background: white;
transition: all 0.2s ease;
position: relative;
flex-shrink: 0;
}
.checkbox-item:hover .checkbox-custom {
border-color: #667eea;
}
.checkbox-item input[type="checkbox"]:checked + .checkbox-custom {
background: #667eea;
border-color: #667eea;
}
.checkbox-item input[type="checkbox"]:checked + .checkbox-custom::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.checkbox-label {
color: #374151;
font-size: 0.95rem;
}
/* Form Actions */
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
padding-top: 1rem; padding: 2rem;
border-top: 1px solid #e9ecef; background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-top: 2rem;
} }
/* Button styles now centralized in main style.css */ /* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
/* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.announcement-form-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.form-row { .form-row {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -336,6 +564,36 @@ document.addEventListener('DOMContentLoaded', function() {
.checkbox-list { .checkbox-list {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.form-actions {
flex-direction: column;
}
.btn {
width: 100%;
justify-content: center;
}
} }
/* 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> </style>
{% endblock %} {% endblock %}

View File

@@ -1,19 +1,35 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="content-header"> <div class="announcements-container">
<div class="header-row"> <!-- Header Section -->
<h1>System Announcements</h1> <div class="page-header">
<a href="{{ url_for('announcements.create') }}" class="btn btn-md btn-primary"> <div class="header-content">
<i class="icon"></i> New Announcement <div class="header-left">
</a> <h1 class="page-title">
<span class="page-icon"><i class="ti ti-speakerphone"></i></span>
System Announcements
</h1>
<p class="page-subtitle">Manage system-wide announcements and notifications</p>
</div>
<div class="header-actions">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Dashboard
</a>
<a href="{{ url_for('announcements.create') }}" class="btn btn-primary">
<i class="ti ti-plus"></i>
New Announcement
</a>
</div>
</div>
</div> </div>
</div>
<div class="content-body"> <!-- Announcements Table -->
{% if announcements.items %} {% if announcements.items %}
<div class="table-container"> <div class="card">
<table class="data-table"> <div class="card-body no-padding">
<table class="table">
<thead> <thead>
<tr> <tr>
<th>Title</th> <th>Title</th>
@@ -28,62 +44,72 @@
</thead> </thead>
<tbody> <tbody>
{% for announcement in announcements.items %} {% for announcement in announcements.items %}
<tr class="{% if not announcement.is_active %}inactive{% endif %}"> <tr class="{% if not announcement.is_active %}inactive-row{% endif %}">
<td> <td>
<strong>{{ announcement.title }}</strong> <div class="announcement-title">
{% if announcement.is_urgent %} <strong>{{ announcement.title }}</strong>
<span class="badge badge-danger">URGENT</span> {% if announcement.is_urgent %}
{% endif %} <span class="badge badge-urgent">URGENT</span>
{% endif %}
</div>
</td> </td>
<td> <td>
<span class="badge badge-{{ announcement.announcement_type }}"> <span class="type-badge type-{{ announcement.announcement_type }}">
{{ announcement.announcement_type.title() }} {{ announcement.announcement_type.title() }}
</span> </span>
</td> </td>
<td> <td>
{% if announcement.is_active %} {% if announcement.is_active %}
{% if announcement.is_visible_now() %} {% if announcement.is_visible_now() %}
<span class="badge badge-success">Active</span> <span class="status-badge status-active">Active</span>
{% else %} {% else %}
<span class="badge badge-warning">Scheduled</span> <span class="status-badge status-scheduled">Scheduled</span>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="badge badge-secondary">Inactive</span> <span class="status-badge status-inactive">Inactive</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if announcement.start_date %} {% if announcement.start_date %}
{{ announcement.start_date.strftime('%Y-%m-%d %H:%M') }} <span class="date-text">{{ announcement.start_date.strftime('%Y-%m-%d %H:%M') }}</span>
{% else %} {% else %}
<em>Immediate</em> <em class="text-muted">Immediate</em>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if announcement.end_date %} {% if announcement.end_date %}
{{ announcement.end_date.strftime('%Y-%m-%d %H:%M') }} <span class="date-text">{{ announcement.end_date.strftime('%Y-%m-%d %H:%M') }}</span>
{% else %} {% else %}
<em>No expiry</em> <em class="text-muted">No expiry</em>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if announcement.target_all_users %} {% if announcement.target_all_users %}
All Users <span class="target-badge target-all">
<i class="ti ti-users"></i>
All Users
</span>
{% else %} {% else %}
<span class="text-muted">Targeted</span> <span class="target-badge target-specific">
<i class="ti ti-target"></i>
Targeted
</span>
{% endif %} {% endif %}
</td> </td>
<td>{{ announcement.created_at.strftime('%Y-%m-%d') }}</td> <td>
<span class="date-text">{{ announcement.created_at.strftime('%Y-%m-%d') }}</span>
</td>
<td> <td>
<div class="action-buttons"> <div class="action-buttons">
<a href="{{ url_for('announcements.edit', id=announcement.id) }}" <a href="{{ url_for('announcements.edit', id=announcement.id) }}"
class="btn btn-sm btn-outline-primary" title="Edit"> class="btn-icon" title="Edit">
✏️ <i class="ti ti-pencil"></i>
</a> </a>
<form method="POST" action="{{ url_for('announcements.delete', id=announcement.id) }}" <form method="POST" action="{{ url_for('announcements.delete', id=announcement.id) }}"
style="display: inline-block;" style="display: inline-block;"
onsubmit="return confirm('Are you sure you want to delete this announcement?')"> onsubmit="return confirm('Are you sure you want to delete this announcement?')">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Delete"> <button type="submit" class="btn-icon danger" title="Delete">
🗑️ <i class="ti ti-trash"></i>
</button> </button>
</form> </form>
</div> </div>
@@ -93,15 +119,20 @@
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<!-- Pagination --> <!-- Pagination -->
{% if announcements.pages > 1 %} {% if announcements.pages > 1 %}
<div class="pagination-container"> <div class="pagination-container">
<div class="pagination"> <div class="pagination">
{% if announcements.has_prev %} {% if announcements.has_prev %}
<a href="{{ url_for('announcements.index', page=announcements.prev_num) }}" class="page-link">« Previous</a> <a href="{{ url_for('announcements.index', page=announcements.prev_num) }}" class="page-link">
{% endif %} <i class="ti ti-chevron-left"></i>
Previous
</a>
{% endif %}
<div class="page-numbers">
{% for page_num in announcements.iter_pages() %} {% for page_num in announcements.iter_pages() %}
{% if page_num %} {% if page_num %}
{% if page_num != announcements.page %} {% if page_num != announcements.page %}
@@ -110,56 +141,452 @@
<span class="page-link current">{{ page_num }}</span> <span class="page-link current">{{ page_num }}</span>
{% endif %} {% endif %}
{% else %} {% else %}
<span class="page-link"></span> <span class="page-dots">...</span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if announcements.has_next %}
<a href="{{ url_for('announcements.index', page=announcements.next_num) }}" class="page-link">Next »</a>
{% endif %}
</div> </div>
{% if announcements.has_next %}
<a href="{{ url_for('announcements.index', page=announcements.next_num) }}" class="page-link">
Next
<i class="ti ti-chevron-right"></i>
</a>
{% endif %}
</div> </div>
{% endif %}
<div class="pagination-info">
Showing {{ announcements.per_page * (announcements.page - 1) + 1 }} -
{{ announcements.per_page * (announcements.page - 1) + announcements.items|length }} of {{ announcements.total }} announcements
</div>
</div>
{% endif %}
{% else %} {% else %}
<div class="empty-state"> <!-- Empty State -->
<h3>No announcements found</h3> <div class="empty-state">
<p>Create your first announcement to communicate with users.</p> <div class="empty-icon"><i class="ti ti-speakerphone"></i></div>
<a href="{{ url_for('announcements.create') }}" class="btn btn-primary"> <h3 class="empty-title">No announcements found</h3>
Create Announcement <p class="empty-message">Create your first announcement to communicate with users.</p>
</a> <a href="{{ url_for('announcements.create') }}" class="btn btn-primary">
</div> <i class="ti ti-plus"></i>
Create Announcement
</a>
</div>
{% endif %} {% endif %}
</div> </div>
<style> <style>
.inactive { /* Container */
.announcements-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
.header-actions {
display: flex;
gap: 1rem;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-body {
padding: 1.5rem;
}
.card-body.no-padding {
padding: 0;
}
/* Table */
.table {
width: 100%;
border-collapse: collapse;
margin: 0;
}
.table th,
.table td {
padding: 1rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.table th {
background: #f8f9fa;
font-weight: 600;
color: #374151;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.8rem;
}
.table tr:hover {
background: #f8f9fa;
}
.inactive-row {
opacity: 0.6; opacity: 0.6;
} }
.badge { /* Announcement Title */
padding: 2px 8px; .announcement-title {
border-radius: 12px; display: flex;
font-size: 0.75em; align-items: center;
font-weight: bold; gap: 0.5rem;
text-transform: uppercase;
} }
.badge-info { background: #17a2b8; color: white; } /* Badges */
.badge-warning { background: #ffc107; color: #212529; } .badge,
.badge-success { background: #28a745; color: white; } .status-badge,
.badge-danger { background: #dc3545; color: white; } .type-badge,
.badge-secondary { background: #6c757d; color: white; } .target-badge {
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.badge-urgent {
background: #fee2e2;
color: #991b1b;
}
/* Type Badges */
.type-info {
background: #dbeafe;
color: #1e40af;
}
.type-warning {
background: #fef3c7;
color: #92400e;
}
.type-success {
background: #d1fae5;
color: #065f46;
}
.type-danger {
background: #fee2e2;
color: #991b1b;
}
/* Status Badges */
.status-active {
background: #d1fae5;
color: #065f46;
}
.status-scheduled {
background: #fef3c7;
color: #92400e;
}
.status-inactive {
background: #e5e7eb;
color: #374151;
}
/* Target Badges */
.target-badge {
font-size: 0.875rem;
}
.target-all {
background: #ede9fe;
color: #5b21b6;
}
.target-specific {
background: #dbeafe;
color: #1e40af;
}
/* Date Text */
.date-text {
color: #6b7280;
font-size: 0.9rem;
}
.text-muted {
color: #9ca3af;
font-style: italic;
}
/* Action Buttons */
.action-buttons { .action-buttons {
display: flex; display: flex;
gap: 5px; gap: 0.5rem;
} }
.btn-icon {
color: #6b7280;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
cursor: pointer;
}
.btn-icon:hover {
background: #667eea;
color: white;
transform: translateY(-1px);
}
.btn-icon.danger:hover {
background: #dc2626;
}
/* Pagination */
.pagination-container {
padding: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
background: white;
border-radius: 12px;
border: 1px solid #e5e7eb;
}
.pagination {
display: flex;
align-items: center;
gap: 0.5rem;
}
.page-numbers {
display: flex;
gap: 0.25rem;
}
.page-link {
padding: 0.5rem 1rem;
border: 1px solid #e5e7eb;
color: #667eea;
text-decoration: none;
border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.page-link:hover {
background: #f3f4f6;
border-color: #667eea;
}
.page-link.current {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-color: transparent;
}
.page-dots {
padding: 0.5rem;
color: #6b7280;
}
.pagination-info {
color: #6b7280;
font-size: 0.875rem;
}
/* Empty State */
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 4rem 2rem;
color: #6c757d; background: white;
border-radius: 12px;
border: 1px solid #e5e7eb;
} }
.empty-icon {
font-size: 4rem;
margin-bottom: 1.5rem;
opacity: 0.3;
}
.empty-title {
font-size: 1.75rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 0.5rem;
}
.empty-message {
font-size: 1.1rem;
color: #6b7280;
margin-bottom: 2rem;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
/* Responsive Design */
@media (max-width: 768px) {
.announcements-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.header-actions {
width: 100%;
flex-direction: column;
}
.table {
font-size: 0.8rem;
}
.table th,
.table td {
padding: 0.5rem;
}
.action-buttons {
flex-direction: column;
gap: 0.25rem;
}
.pagination-container {
flex-direction: column;
}
}
/* 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> </style>
{% endblock %} {% endblock %}

View File

@@ -1,54 +1,69 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="management-container"> <div class="branding-container">
<div class="management-header"> <!-- Header Section -->
<h1>🎨 Branding Settings</h1> <div class="page-header">
<div class="management-actions"> <div class="header-content">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary"><i class="ti ti-arrow-left"></i> Back to Dashboard</a> <div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-palette"></i></span>
Branding Settings
</h1>
<p class="page-subtitle">Customize the appearance and branding of {{ branding.app_name }}</p>
</div>
<div class="header-actions">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Dashboard
</a>
</div>
</div> </div>
</div> </div>
<p class="subtitle">Customize the appearance and branding of {{ branding.app_name }}</p>
<!-- Current Branding Preview --> <!-- Current Branding Preview -->
<div class="management-section"> <div class="card preview-card">
<h2>👁️ Current Branding Preview</h2> <div class="card-header">
<div class="management-card branding-preview-card"> <h2 class="card-title">
<div class="card-header"> <span class="icon"><i class="ti ti-eye"></i></span>
<h3>Live Preview</h3> Current Branding Preview
</div> </h2>
<div class="card-body"> </div>
<div class="preview-demo"> <div class="card-body">
<div class="demo-header"> <div class="preview-demo">
{% if branding.logo_filename %} <div class="demo-header">
<img src="{{ url_for('static', filename='uploads/branding/' + branding.logo_filename) }}" {% if branding.logo_filename %}
alt="{{ branding.logo_alt_text }}" <img src="{{ url_for('static', filename='uploads/branding/' + branding.logo_filename) }}"
class="demo-logo"> alt="{{ branding.logo_alt_text }}"
{% else %} class="demo-logo">
<span class="demo-text-logo">{{ branding.app_name }}</span> {% else %}
{% endif %} <span class="demo-text-logo">{{ branding.app_name }}</span>
</div> {% endif %}
<div class="demo-content"> </div>
<p>Welcome to {{ branding.app_name }}</p> <div class="demo-content">
<button class="btn btn-primary" style="background-color: {{ branding.primary_color }}; border-color: {{ branding.primary_color }};"> <p>Welcome to {{ branding.app_name }}</p>
Sample Button <button class="btn btn-primary" style="background-color: {{ branding.primary_color }}; border-color: {{ branding.primary_color }};">
</button> Sample Button
<a href="#" style="color: {{ branding.primary_color }};">Sample Link</a> </button>
</div> <a href="#" style="color: {{ branding.primary_color }};">Sample Link</a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Branding Settings Form --> <!-- Branding Settings Form -->
<div class="management-section"> <div class="card">
<h2><i class="ti ti-tool"></i> Branding Configuration</h2> <div class="card-header">
<div class="management-card"> <h2 class="card-title">
<span class="icon"><i class="ti ti-settings"></i></span>
Branding Configuration
</h2>
</div>
<div class="card-body">
<form method="POST" enctype="multipart/form-data" class="settings-form"> <form method="POST" enctype="multipart/form-data" class="settings-form">
<!-- Application Name --> <!-- Application Name -->
<div class="form-section"> <div class="form-section">
<h3>📝 Basic Information</h3> <h3><i class="ti ti-forms"></i> Basic Information</h3>
<div class="form-group"> <div class="form-group">
<label for="app_name">Application Name</label> <label for="app_name">Application Name</label>
<input type="text" id="app_name" name="app_name" <input type="text" id="app_name" name="app_name"
@@ -56,7 +71,7 @@
class="form-control" class="form-control"
placeholder="TimeTrack" placeholder="TimeTrack"
required> required>
<small class="form-text text-muted"> <small class="form-text">
This name will appear in the title, navigation, and throughout the interface. This name will appear in the title, navigation, and throughout the interface.
</small> </small>
</div> </div>
@@ -67,7 +82,7 @@
value="{{ branding.logo_alt_text }}" value="{{ branding.logo_alt_text }}"
class="form-control" class="form-control"
placeholder="Company Logo"> placeholder="Company Logo">
<small class="form-text text-muted"> <small class="form-text">
Text displayed when the logo cannot be loaded (accessibility). Text displayed when the logo cannot be loaded (accessibility).
</small> </small>
</div> </div>
@@ -75,7 +90,7 @@
<!-- Visual Assets --> <!-- Visual Assets -->
<div class="form-section"> <div class="form-section">
<h3>🖼️ Visual Assets</h3> <h3><i class="ti ti-photo"></i> Visual Assets</h3>
<div class="form-row"> <div class="form-row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="logo_file">Logo Image</label> <label for="logo_file">Logo Image</label>
@@ -90,7 +105,7 @@
<span class="asset-label">Current logo</span> <span class="asset-label">Current logo</span>
</div> </div>
{% endif %} {% endif %}
<small class="form-text text-muted"> <small class="form-text">
PNG, JPG, GIF, SVG. Recommended: 200x50px PNG, JPG, GIF, SVG. Recommended: 200x50px
</small> </small>
</div> </div>
@@ -108,7 +123,7 @@
<span class="asset-label">Current favicon</span> <span class="asset-label">Current favicon</span>
</div> </div>
{% endif %} {% endif %}
<small class="form-text text-muted"> <small class="form-text">
ICO, PNG. Recommended: 16x16px or 32x32px ICO, PNG. Recommended: 16x16px or 32x32px
</small> </small>
</div> </div>
@@ -117,7 +132,7 @@
<!-- Theme Settings --> <!-- Theme Settings -->
<div class="form-section"> <div class="form-section">
<h3>🎨 Theme Settings</h3> <h3><i class="ti ti-color-swatch"></i> Theme Settings</h3>
<div class="form-group"> <div class="form-group">
<label for="primary_color">Primary Color</label> <label for="primary_color">Primary Color</label>
<div class="color-picker-wrapper"> <div class="color-picker-wrapper">
@@ -130,7 +145,7 @@
pattern="^#[0-9A-Fa-f]{6}$" pattern="^#[0-9A-Fa-f]{6}$"
placeholder="#007bff"> placeholder="#007bff">
</div> </div>
<small class="form-text text-muted"> <small class="form-text">
This color will be used for buttons, links, and other UI elements. This color will be used for buttons, links, and other UI elements.
</small> </small>
</div> </div>
@@ -138,7 +153,7 @@
<!-- Imprint/Legal Page --> <!-- Imprint/Legal Page -->
<div class="form-section"> <div class="form-section">
<h3>⚖️ Imprint / Legal Page</h3> <h3><i class="ti ti-scale"></i> Imprint / Legal Page</h3>
<div class="form-group"> <div class="form-group">
<label class="toggle-label"> <label class="toggle-label">
<input type="checkbox" name="imprint_enabled" id="imprint_enabled" <input type="checkbox" name="imprint_enabled" id="imprint_enabled"
@@ -146,7 +161,7 @@
<span class="toggle-slider"></span> <span class="toggle-slider"></span>
<span class="toggle-text">Enable Imprint Page</span> <span class="toggle-text">Enable Imprint Page</span>
</label> </label>
<small class="form-text text-muted"> <small class="form-text">
When enabled, an "Imprint" link will appear in the footer linking to your custom legal page. When enabled, an "Imprint" link will appear in the footer linking to your custom legal page.
</small> </small>
</div> </div>
@@ -158,7 +173,7 @@
value="{{ branding.imprint_title or 'Imprint' }}" value="{{ branding.imprint_title or 'Imprint' }}"
class="form-control" class="form-control"
placeholder="Imprint"> placeholder="Imprint">
<small class="form-text text-muted"> <small class="form-text">
The title that will be displayed on the imprint page. The title that will be displayed on the imprint page.
</small> </small>
</div> </div>
@@ -169,7 +184,7 @@
class="form-control content-editor" class="form-control content-editor"
rows="15" rows="15"
placeholder="Enter your imprint/legal information here...">{{ branding.imprint_content or '' }}</textarea> placeholder="Enter your imprint/legal information here...">{{ branding.imprint_content or '' }}</textarea>
<small class="form-text text-muted"> <small class="form-text">
You can use HTML to format your content. Common tags: &lt;h2&gt;, &lt;h3&gt;, &lt;p&gt;, &lt;strong&gt;, &lt;br&gt;, &lt;a href=""&gt; You can use HTML to format your content. Common tags: &lt;h2&gt;, &lt;h3&gt;, &lt;p&gt;, &lt;strong&gt;, &lt;br&gt;, &lt;a href=""&gt;
</small> </small>
</div> </div>
@@ -178,7 +193,10 @@
<!-- Save Button --> <!-- Save Button -->
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">💾 Save Branding Settings</button> <button type="submit" class="btn btn-primary">
<i class="ti ti-device-floppy"></i>
Save Branding Settings
</button>
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">Cancel</a> <a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">Cancel</a>
</div> </div>
</form> </form>
@@ -187,8 +205,99 @@
</div> </div>
<style> <style>
/* Branding-specific styles */ /* Container */
.branding-preview-card { .branding-container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: rotate 8s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
background: #f8f9fa;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
/* Preview Card */
.preview-card {
margin-bottom: 2rem; margin-bottom: 2rem;
} }
@@ -202,7 +311,7 @@
.demo-header { .demo-header {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
padding-bottom: 1rem; padding-bottom: 1rem;
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #e5e7eb;
} }
.demo-logo { .demo-logo {
@@ -213,8 +322,8 @@
.demo-text-logo { .demo-text-logo {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 700;
color: #333; color: #1f2937;
} }
.demo-content { .demo-content {
@@ -222,7 +331,7 @@
} }
.demo-content p { .demo-content p {
color: #6c757d; color: #6b7280;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@@ -230,12 +339,80 @@
margin-right: 1rem; margin-right: 1rem;
} }
/* Current assets display */ /* Form Sections */
.form-section {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #e5e7eb;
}
.form-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
.form-section h3 {
margin-bottom: 1rem;
color: #1f2937;
font-size: 1.1rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
.form-section h3 i {
font-size: 1.25rem;
}
/* Form Controls */
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
font-weight: 600;
margin-bottom: 0.5rem;
color: #374151;
}
.form-control {
width: 100%;
padding: 0.625rem 1rem;
border: 1px solid #e5e7eb;
border-radius: 8px;
font-size: 1rem;
transition: all 0.2s ease;
}
.form-control:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.form-text {
display: block;
margin-top: 0.25rem;
font-size: 0.875rem;
color: #6b7280;
}
/* File Input */
.form-control-file {
display: block;
width: 100%;
padding: 0.375rem 0;
font-size: 0.875rem;
}
/* Current Assets */
.current-asset { .current-asset {
margin-top: 0.5rem; margin-top: 0.5rem;
padding: 0.75rem; padding: 0.75rem;
background: #f8f9fa; background: #f8f9fa;
border-radius: 4px; border-radius: 8px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
@@ -255,10 +432,10 @@
.asset-label { .asset-label {
font-size: 0.875rem; font-size: 0.875rem;
color: #6c757d; color: #6b7280;
} }
/* Color picker styling */ /* Color Picker */
.color-picker-wrapper { .color-picker-wrapper {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
@@ -270,8 +447,8 @@
width: 60px; width: 60px;
height: 38px; height: 38px;
padding: 0.25rem; padding: 0.25rem;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 4px; border-radius: 8px;
cursor: pointer; cursor: pointer;
} }
@@ -280,58 +457,8 @@
font-family: monospace; font-family: monospace;
} }
/* Form sections */ /* Toggle Switch */
.form-section { .toggle-label {
margin-bottom: 2rem;
padding-bottom: 2rem;
border-bottom: 1px solid #dee2e6;
}
.form-section:last-child {
border-bottom: none;
margin-bottom: 0;
}
.form-section h3 {
margin-bottom: 1rem;
color: #495057;
font-size: 1.1rem;
font-weight: 600;
}
/* File input styling */
.form-control-file {
display: block;
width: 100%;
padding: 0.375rem 0;
}
/* Form row for two-column layout */
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.form-row > .col-md-6 {
flex: 0 0 50%;
max-width: 50%;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
@media (max-width: 768px) {
.form-row > .col-md-6 {
flex: 0 0 100%;
max-width: 100%;
}
}
/* Sync color inputs */
/* Toggle label styling - ensuring proper alignment */
.form-group .toggle-label {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
@@ -349,7 +476,7 @@
display: inline-block; display: inline-block;
width: 50px; width: 50px;
height: 24px; height: 24px;
background: #ccc; background: #e5e7eb;
border-radius: 24px; border-radius: 24px;
transition: background 0.3s; transition: background 0.3s;
flex-shrink: 0; flex-shrink: 0;
@@ -365,10 +492,11 @@
top: 2px; top: 2px;
left: 2px; left: 2px;
transition: transform 0.3s; transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider { .toggle-label input[type="checkbox"]:checked + .toggle-slider {
background: var(--primary-color); background: #667eea;
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider::before { .toggle-label input[type="checkbox"]:checked + .toggle-slider::before {
@@ -377,18 +505,18 @@
.toggle-text { .toggle-text {
font-weight: 500; font-weight: 500;
color: #495057; color: #1f2937;
line-height: 1; line-height: 1;
} }
/* Content editor styling */ /* Content Editor */
.content-editor { .content-editor {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.5; line-height: 1.5;
background: #f8f9fa; background: #f8f9fa;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 4px; border-radius: 8px;
padding: 0.75rem; padding: 0.75rem;
resize: vertical; resize: vertical;
} }
@@ -400,6 +528,108 @@
border-radius: 8px; border-radius: 8px;
transition: all 0.3s ease; transition: all 0.3s ease;
} }
/* Form Row */
.form-row {
display: flex;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.form-row > .col-md-6 {
flex: 0 0 50%;
max-width: 50%;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
/* Form Actions */
.form-actions {
display: flex;
gap: 1rem;
padding-top: 1.5rem;
border-top: 1px solid #e5e7eb;
margin-top: 2rem;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
/* Responsive Design */
@media (max-width: 768px) {
.branding-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.form-row > .col-md-6 {
flex: 0 0 100%;
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> </style>
<script> <script>

View File

@@ -1,313 +1,591 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="companies-admin-container">
<div class="header-section"> <!-- Header Section -->
<h1><i class="ti ti-building"></i> System Admin - All Companies</h1> <div class="page-header">
<p class="subtitle">Manage companies across the entire system</p> <div class="header-content">
<div class="header-actions"> <div class="header-left">
<a href="/setup" class="btn btn-md btn-success">+ Add New Company</a> <h1 class="page-title">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-md btn-secondary"><i class="ti ti-arrow-left"></i> Back to Dashboard</a> <span class="page-icon"><i class="ti ti-building"></i></span>
All Companies
</h1>
<p class="page-subtitle">Manage companies across the entire system</p>
</div>
<div class="header-actions">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Dashboard
</a>
<a href="/setup" class="btn btn-primary">
<i class="ti ti-plus"></i>
Add New Company
</a>
</div>
</div> </div>
</div> </div>
<!-- Companies Table --> <!-- Summary Statistics -->
{% if companies.items %} <div class="stats-section">
<div class="table-section"> <div class="stat-card">
<table class="table"> <div class="stat-value">{{ companies.total }}</div>
<thead> <div class="stat-label">Total Companies</div>
<tr> </div>
<th>Company Name</th> <div class="stat-card">
<th>Type</th> <div class="stat-value">{{ companies.items | selectattr('is_personal') | list | length }}</div>
<th>Users</th> <div class="stat-label">Personal Companies</div>
<th>Admins</th> </div>
<th>Status</th> <div class="stat-card">
<th>Created</th> <div class="stat-value">{{ companies.items | rejectattr('is_personal') | list | length }}</div>
<th>Actions</th> <div class="stat-label">Business Companies</div>
</tr> </div>
</thead> <div class="stat-card">
<tbody> <div class="stat-value">{{ companies.items | selectattr('is_active') | list | length }}</div>
{% for company in companies.items %} <div class="stat-label">Active Companies</div>
<tr class="{% if not company.is_active %}inactive-company{% endif %}"> </div>
<td>
<strong>{{ company.name }}</strong>
{% if company.slug %}
<br><small class="text-muted">{{ company.slug }}</small>
{% endif %}
</td>
<td>
{% if company.is_personal %}
<span class="badge badge-freelancer">Freelancer</span>
{% else %}
<span class="badge badge-company">Company</span>
{% endif %}
</td>
<td>
<span class="stat-number">{{ company_stats[company.id]['user_count'] }}</span>
<small>users</small>
</td>
<td>
<span class="stat-number">{{ company_stats[company.id]['admin_count'] }}</span>
<small>admins</small>
</td>
<td>
{% if company.is_active %}
<span class="status-badge status-active">Active</span>
{% else %}
<span class="status-badge status-inactive">Inactive</span>
{% endif %}
</td>
<td>{{ company.created_at.strftime('%Y-%m-%d') }}</td>
<td>
<div class="action-buttons">
<a href="{{ url_for('system_admin.system_admin_company_detail', company_id=company.id) }}"
class="btn btn-sm btn-primary">View Details</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
<!-- Pagination --> <!-- Main Content -->
{% if companies.pages > 1 %} <div class="content-section">
<div class="pagination-section"> {% if companies.items %}
<div class="pagination"> <!-- Companies Table -->
{% if companies.has_prev %} <div class="table-container">
<a href="{{ url_for('system_admin.system_admin_companies', page=companies.prev_num) }}" class="page-link"><i class="ti ti-arrow-left"></i> Previous</a> <table class="data-table">
{% endif %} <thead>
<tr>
{% for page_num in companies.iter_pages() %} <th>Company</th>
{% if page_num %} <th>Type</th>
{% if page_num != companies.page %} <th>Users</th>
<a href="{{ url_for('system_admin.system_admin_companies', page=page_num) }}" class="page-link">{{ page_num }}</a> <th>Admins</th>
{% else %} <th>Status</th>
<span class="page-link current">{{ page_num }}</span> <th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for company in companies.items %}
<tr class="{% if not company.is_active %}inactive-row{% endif %}">
<td>
<div class="company-cell">
<div class="company-name">{{ company.name }}</div>
{% if company.slug %}
<div class="company-slug">{{ company.slug }}</div>
{% endif %}
</div>
</td>
<td>
{% if company.is_personal %}
<span class="badge badge-freelancer">Freelancer</span>
{% else %}
<span class="badge badge-company">Company</span>
{% endif %}
</td>
<td>
<div class="stat-cell">
<span class="stat-number">{{ company_stats[company.id]['user_count'] }}</span>
<span class="stat-label">users</span>
</div>
</td>
<td>
<div class="stat-cell">
<span class="stat-number">{{ company_stats[company.id]['admin_count'] }}</span>
<span class="stat-label">admins</span>
</div>
</td>
<td>
{% if company.is_active %}
<span class="status-badge status-active">Active</span>
{% else %}
<span class="status-badge status-inactive">Inactive</span>
{% endif %}
</td>
<td>
<span class="date-text">{{ company.created_at.strftime('%Y-%m-%d') }}</span>
</td>
<td>
<div class="table-actions">
<a href="{{ url_for('system_admin.system_admin_company_detail', company_id=company.id) }}"
class="btn-icon" title="View Details">
<i class="ti ti-eye"></i>
</a>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Pagination -->
{% if companies.pages > 1 %}
<div class="pagination-container">
<div class="pagination">
{% if companies.has_prev %}
<a href="{{ url_for('system_admin.system_admin_companies', page=companies.prev_num) }}"
class="page-link">
<i class="ti ti-chevron-left"></i>
Previous
</a>
{% endif %} {% endif %}
{% else %}
<span class="page-link"></span> <div class="page-numbers">
{% endif %} {% for page_num in companies.iter_pages() %}
{% endfor %} {% if page_num %}
{% if page_num != companies.page %}
{% if companies.has_next %} <a href="{{ url_for('system_admin.system_admin_companies', page=page_num) }}"
<a href="{{ url_for('system_admin.system_admin_companies', page=companies.next_num) }}" class="page-link">Next <i class="ti ti-arrow-right"></i></a> class="page-link">{{ page_num }}</a>
{% else %}
<span class="page-link current">{{ page_num }}</span>
{% endif %}
{% else %}
<span class="page-dots">...</span>
{% endif %}
{% endfor %}
</div>
{% if companies.has_next %}
<a href="{{ url_for('system_admin.system_admin_companies', page=companies.next_num) }}"
class="page-link">
Next
<i class="ti ti-chevron-right"></i>
</a>
{% endif %}
</div>
<div class="pagination-info">
Showing {{ companies.per_page * (companies.page - 1) + 1 }} -
{{ companies.per_page * (companies.page - 1) + companies.items|length }} of {{ companies.total }} companies
</div>
</div>
{% endif %} {% endif %}
</div>
<p class="pagination-info">
Showing {{ companies.per_page * (companies.page - 1) + 1 }} -
{{ companies.per_page * (companies.page - 1) + companies.items|length }} of {{ companies.total }} companies
</p>
</div>
{% endif %}
{% else %} {% else %}
<div class="empty-state"> <!-- Empty State -->
<h3>No companies found</h3> <div class="empty-state">
<p>No companies exist in the system yet.</p> <div class="empty-icon"><i class="ti ti-building-community"></i></div>
</div> <h3 class="empty-title">No Companies Yet</h3>
{% endif %} <p class="empty-message">No companies exist in the system.</p>
<a href="/setup" class="btn btn-primary">
<!-- Company Statistics Summary --> <i class="ti ti-plus"></i>
<div class="summary-section"> Create First Company
<h3><i class="ti ti-chart-bar"></i> Company Summary</h3> </a>
<div class="summary-grid">
<div class="summary-card">
<h4>Total Companies</h4>
<p class="summary-number">{{ companies.total }}</p>
</div> </div>
<div class="summary-card"> {% endif %}
<h4>Personal Companies</h4>
<p class="summary-number">{{ companies.items | selectattr('is_personal') | list | length }}</p>
</div>
<div class="summary-card">
<h4>Business Companies</h4>
<p class="summary-number">{{ companies.items | rejectattr('is_personal') | list | length }}</p>
</div>
<div class="summary-card">
<h4>Active Companies</h4>
<p class="summary-number">{{ companies.items | selectattr('is_active') | list | length }}</p>
</div>
</div>
</div> </div>
</div> </div>
<style> <style>
.header-section { /* Container */
.companies-admin-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
.header-actions {
display: flex;
gap: 1rem;
}
/* Stats Section */
.stats-section {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.subtitle { .stat-card {
color: #6c757d;
margin-bottom: 1rem;
}
.table-section {
background: white; background: white;
border-radius: 8px; padding: 1.5rem;
overflow: hidden; border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); text-align: center;
margin-bottom: 2rem; border: 1px solid #e5e7eb;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
} }
.table { .stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
}
.stat-value {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: #667eea;
}
.stat-label {
font-size: 0.9rem;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
/* Content Section */
.content-section {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
overflow: hidden;
}
/* Table Container */
.table-container {
overflow-x: auto;
}
.data-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
margin: 0;
} }
.table th, .data-table th,
.table td { .data-table td {
padding: 1rem; padding: 1rem 1.5rem;
text-align: left; text-align: left;
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #e5e7eb;
} }
.table th { .data-table th {
background: #f8f9fa; background: #f8f9fa;
font-weight: 600; font-weight: 600;
color: #495057; color: #374151;
}
.inactive-company {
background-color: #f8f9fa !important;
opacity: 0.7;
}
.text-muted {
color: #6c757d;
font-size: 0.875rem; font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
} }
.data-table tr:hover {
background: #f8f9fa;
}
.inactive-row {
opacity: 0.6;
}
/* Company Cell */
.company-cell {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.company-name {
font-weight: 600;
color: #1f2937;
}
.company-slug {
font-size: 0.875rem;
color: #6b7280;
}
/* Stat Cell */
.stat-cell {
display: flex;
align-items: baseline;
gap: 0.25rem;
}
.stat-cell .stat-number {
font-weight: 700;
color: #667eea;
font-size: 1.1rem;
}
.stat-cell .stat-label {
font-size: 0.875rem;
color: #6b7280;
}
/* Date Text */
.date-text {
color: #6b7280;
font-size: 0.95rem;
}
/* Badges */
.badge { .badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.badge-company { .badge-company {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.badge-freelancer { .badge-freelancer {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
}
.stat-number {
font-weight: 600;
color: #007bff;
} }
/* Status Badges */
.status-badge { .status-badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.status-active { .status-active {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
.status-inactive { .status-inactive {
background: #f8d7da; background: #fee2e2;
color: #721c24; color: #991b1b;
} }
.action-buttons { /* Table Actions */
.table-actions {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
} }
/* Button styles now centralized in main style.css */ .btn-icon {
color: #6b7280;
display: inline-flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 8px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
transition: all 0.2s ease;
cursor: pointer;
}
.pagination-section { .btn-icon:hover {
margin: 2rem 0; background: #667eea;
color: white;
transform: translateY(-1px);
}
/* Pagination */
.pagination-container {
padding: 1.5rem;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
gap: 1rem; gap: 1rem;
border-top: 1px solid #e5e7eb;
} }
.pagination { .pagination {
display: flex;
align-items: center;
gap: 0.5rem;
}
.page-numbers {
display: flex; display: flex;
gap: 0.25rem; gap: 0.25rem;
} }
.page-link { .page-link {
padding: 0.5rem 0.75rem; padding: 0.5rem 1rem;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
color: #007bff; color: #667eea;
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
} }
.page-link:hover { .page-link:hover {
background: #e9ecef; background: #f3f4f6;
border-color: #667eea;
} }
.page-link.current { .page-link.current {
background: #007bff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
border-color: #007bff; border-color: transparent;
}
.page-dots {
padding: 0.5rem;
color: #6b7280;
} }
.pagination-info { .pagination-info {
color: #6c757d; color: #6b7280;
margin: 0; font-size: 0.875rem;
font-size: 0.9rem;
} }
/* Empty State */
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 4rem 2rem;
color: #6c757d;
} }
.summary-section { .empty-icon {
background: #f8f9fa; font-size: 4rem;
border-radius: 8px;
padding: 2rem;
}
.summary-section h3 {
margin-top: 0;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
color: #495057; opacity: 0.3;
} }
.summary-grid { .empty-title {
display: grid; font-size: 1.75rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); font-weight: 700;
gap: 1rem; color: #1f2937;
margin-bottom: 0.5rem;
} }
.summary-card { .empty-message {
background: white; font-size: 1.1rem;
color: #6b7280;
margin-bottom: 2rem;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px; border-radius: 8px;
padding: 1.5rem;
text-align: center;
border: 1px solid #dee2e6;
}
.summary-card h4 {
margin: 0 0 0.5rem 0;
color: #6c757d;
font-size: 0.875rem;
font-weight: 500;
}
.summary-number {
font-size: 2rem;
font-weight: 600; font-weight: 600;
color: #007bff; font-size: 1rem;
margin: 0; cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
/* Responsive Design */
@media (max-width: 768px) {
.companies-admin-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.header-actions {
width: 100%;
flex-direction: column;
}
.table-container {
margin: 0 -1rem;
}
.data-table {
font-size: 0.875rem;
}
.data-table th,
.data-table td {
padding: 0.75rem;
}
.pagination-container {
flex-direction: column;
}
}
/* Animations */
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.content-section {
animation: slideIn 0.3s ease-out;
} }
</style> </style>
{% endblock %} {% endblock %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,38 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="edit-user-container">
<div class="header-section"> <!-- Header Section -->
<h1>✏️ Edit User: {{ user.username }}</h1> <div class="page-header">
<p class="subtitle">System Administrator - Edit user across companies</p> <div class="header-content">
<a href="{{ url_for('users.system_admin_users') }}" class="btn btn-secondary"><i class="ti ti-arrow-left"></i> Back to Users</a> <div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-user-edit"></i></span>
Edit User: {{ user.username }}
</h1>
<p class="page-subtitle">System Administrator - Edit user across companies</p>
</div>
<div class="header-actions">
<a href="{{ url_for('users.system_admin_users') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Users
</a>
</div>
</div>
</div> </div>
<div class="form-container"> <!-- Main Form -->
<form method="POST"> <form method="POST" class="user-edit-form">
<div class="form-grid"> <div class="form-grid">
<!-- Basic Information --> <!-- Basic Information -->
<div class="form-section"> <div class="card">
<h3>Basic Information</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-user"></i></span>
Basic Information
</h2>
</div>
<div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="username">Username</label> <label for="username">Username</label>
@@ -29,10 +48,17 @@
class="form-control"> class="form-control">
</div> </div>
</div> </div>
</div>
<!-- Company & Team Assignment --> <!-- Company & Team Assignment -->
<div class="form-section"> <div class="card">
<h3>Company & Team</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-building"></i></span>
Company & Team
</h2>
</div>
<div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="company_id">Company</label> <label for="company_id">Company</label>
@@ -60,10 +86,17 @@
</select> </select>
</div> </div>
</div> </div>
</div>
<!-- Role & Permissions --> <!-- Role & Permissions -->
<div class="form-section"> <div class="card">
<h3>Role & Permissions</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-shield-check"></i></span>
Role & Permissions
</h2>
</div>
<div class="card-body">
<div class="form-group"> <div class="form-group">
<label for="role">Role</label> <label for="role">Role</label>
@@ -76,40 +109,54 @@
{% endfor %} {% endfor %}
</select> </select>
{% if user.role == Role.SYSTEM_ADMIN %} {% if user.role == Role.SYSTEM_ADMIN %}
<small class="form-text">⚠️ Warning: This user is a System Administrator</small> <small class="form-text warning-text"><i class="ti ti-alert-triangle"></i> Warning: This user is a System Administrator</small>
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div>
<!-- Account Status --> <!-- Account Status -->
<div class="form-section"> <div class="card">
<h3>Account Status</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-settings"></i></span>
Account Status
</h2>
</div>
<div class="card-body">
<div class="form-group"> <div class="form-group">
<label class="checkbox-label"> <label class="toggle-label">
<input type="checkbox" name="is_verified" <input type="checkbox" name="is_verified"
{% if user.is_verified %}checked{% endif %}> {% if user.is_verified %}checked{% endif %}>
<span class="checkmark"></span> <span class="toggle-slider"></span>
Email Verified <span class="toggle-text">Email Verified</span>
</label> </label>
<small class="form-text">Whether the user's email address has been verified</small> <small class="form-text">Whether the user's email address has been verified</small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="checkbox-label"> <label class="toggle-label">
<input type="checkbox" name="is_blocked" <input type="checkbox" name="is_blocked"
{% if user.is_blocked %}checked{% endif %}> {% if user.is_blocked %}checked{% endif %}>
<span class="checkmark"></span> <span class="toggle-slider"></span>
Account Blocked <span class="toggle-text">Account Blocked</span>
</label> </label>
<small class="form-text">Blocked users cannot log in to the system</small> <small class="form-text">Blocked users cannot log in to the system</small>
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- User Information Display --> <!-- User Information Display -->
<div class="info-section"> <div class="card full-width">
<h3>User Information</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-info-circle"></i></span>
User Information
</h2>
</div>
<div class="card-body">
<div class="info-grid"> <div class="info-grid">
<div class="info-item"> <div class="info-item">
<label>Account Type:</label> <label>Account Type:</label>
@@ -138,26 +185,46 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Form Actions --> <!-- Form Actions -->
<div class="form-actions"> <div class="form-actions">
<button type="submit" class="btn btn-primary">Save Changes</button> <button type="submit" class="btn btn-primary">
<a href="{{ url_for('users.system_admin_users') }}" class="btn btn-secondary">Cancel</a> <i class="ti ti-device-floppy"></i>
Save Changes
{% if user.id != g.user.id and not (user.role == Role.SYSTEM_ADMIN and user.id == g.user.id) %} </button>
<div class="danger-zone"> <a href="{{ url_for('users.system_admin_users') }}" class="btn btn-secondary">Cancel</a>
<h4>Danger Zone</h4> </div>
<p>Permanently delete this user account. This action cannot be undone.</p> </form>
<!-- Danger Zone -->
{% if user.id != g.user.id and not (user.role == Role.SYSTEM_ADMIN and user.id == g.user.id) %}
<div class="danger-zone">
<div class="danger-header">
<h2 class="danger-title">
<i class="ti ti-alert-triangle"></i>
Danger Zone
</h2>
</div>
<div class="danger-content">
<div class="danger-item">
<div class="danger-info">
<h4>Delete User Account</h4>
<p>Permanently delete this user account. This will also delete all their time entries and cannot be undone.</p>
</div>
<div class="danger-actions">
<form method="POST" action="{{ url_for('users.system_admin_delete_user', user_id=user.id) }}" <form method="POST" action="{{ url_for('users.system_admin_delete_user', user_id=user.id) }}"
style="display: inline;"
onsubmit="return confirm('Are you sure you want to delete user \'{{ user.username }}\'? This will also delete all their time entries and cannot be undone.')"> onsubmit="return confirm('Are you sure you want to delete user \'{{ user.username }}\'? This will also delete all their time entries and cannot be undone.')">
<button type="submit" class="btn btn-danger">Delete User</button> <button type="submit" class="btn btn-danger">
<i class="ti ti-trash"></i>
Delete User
</button>
</form> </form>
</div> </div>
{% endif %}
</div> </div>
</form> </div>
</div> </div>
{% endif %}
</div> </div>
<script> <script>
@@ -191,175 +258,402 @@ document.getElementById('company_id').addEventListener('change', function() {
</script> </script>
<style> <style>
.header-section { /* Container */
margin-bottom: 2rem; .edit-user-container {
} max-width: 1200px;
margin: 0 auto;
.subtitle {
color: #6c757d;
margin-bottom: 1rem;
}
.form-container {
max-width: 800px;
background: white;
border-radius: 8px;
padding: 2rem; padding: 2rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
} }
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.header-content {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
}
.page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Form Grid */
.form-grid { .form-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem; gap: 1.5rem;
margin-bottom: 2rem; margin-bottom: 1.5rem;
} }
.form-section { /* Cards */
border: 1px solid #dee2e6; .card {
border-radius: 8px; background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card.full-width {
grid-column: 1 / -1;
}
.card-header {
background: #f8f9fa;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem; padding: 1.5rem;
} }
.form-section h3 { /* Form Elements */
margin-top: 0;
margin-bottom: 1.5rem;
color: #495057;
border-bottom: 2px solid #e9ecef;
padding-bottom: 0.5rem;
}
.form-group { .form-group {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.form-group label { .form-group label {
display: block; display: block;
font-weight: 600;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
font-weight: 500; color: #374151;
color: #495057;
} }
.form-control { .form-control {
width: 100%; width: 100%;
padding: 0.75rem; padding: 0.625rem 1rem;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 4px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; transition: all 0.2s ease;
} }
.form-control:focus { .form-control:focus {
border-color: #007bff; outline: none;
outline: 0; border-color: #667eea;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
} }
.form-text { .form-text {
display: block; display: block;
margin-top: 0.25rem; margin-top: 0.25rem;
font-size: 0.875rem; font-size: 0.875rem;
color: #6c757d; color: #6b7280;
} }
.checkbox-label { .warning-text {
display: flex; color: #dc2626 !important;
font-weight: 500;
}
/* Toggle Switches */
.toggle-label {
display: inline-flex;
align-items: center; align-items: center;
gap: 0.75rem;
cursor: pointer; cursor: pointer;
font-weight: normal; margin-bottom: 0.5rem;
} }
.checkbox-label input[type="checkbox"] { .toggle-label input[type="checkbox"] {
margin-right: 0.5rem; display: none;
margin-bottom: 0;
} }
.info-section { .toggle-slider {
background: #f8f9fa; position: relative;
border-radius: 8px; display: inline-block;
padding: 1.5rem; width: 50px;
margin-bottom: 2rem; height: 24px;
background: #e5e7eb;
border-radius: 24px;
transition: background 0.3s;
flex-shrink: 0;
} }
.info-section h3 { .toggle-slider::before {
margin-top: 0; content: '';
margin-bottom: 1rem; position: absolute;
color: #495057; width: 20px;
height: 20px;
border-radius: 50%;
background: white;
top: 2px;
left: 2px;
transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider {
background: #667eea;
}
.toggle-label input[type="checkbox"]:checked + .toggle-slider::before {
transform: translateX(26px);
}
.toggle-text {
font-weight: 500;
color: #1f2937;
}
/* Info Grid */
.info-grid { .info-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem; gap: 1.5rem;
} }
.info-item { .info-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.25rem; gap: 0.5rem;
} }
.info-item label { .info-item label {
font-weight: 600; font-weight: 600;
color: #6c757d; color: #6b7280;
font-size: 0.875rem; font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
} }
/* Badges */
.badge { .badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
display: inline-block; display: inline-block;
width: fit-content;
} }
.badge-company { .badge-company {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.badge-freelancer { .badge-freelancer {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
.text-success { .text-success {
color: #28a745; color: #10b981;
font-weight: 500;
} }
.text-muted { .text-muted {
color: #6c757d; color: #6b7280;
} }
/* Form Actions */
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
align-items: center; padding: 2rem;
flex-wrap: wrap; background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 2rem;
} }
/* Button styles now centralized in main style.css */ /* Danger Zone */
.danger-zone { .danger-zone {
margin-left: auto; background: #fef2f2;
padding: 1rem; border: 2px solid #fecaca;
border: 2px solid #dc3545; border-radius: 12px;
overflow: hidden;
}
.danger-header {
background: #fee2e2;
padding: 1.5rem;
border-bottom: 1px solid #fecaca;
}
.danger-title {
font-size: 1.25rem;
font-weight: 600;
color: #991b1b;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.danger-content {
padding: 2rem;
}
.danger-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: 2rem;
}
.danger-info h4 {
margin: 0 0 0.5rem 0;
color: #991b1b;
font-weight: 600;
}
.danger-info p {
margin: 0;
color: #7f1d1d;
font-size: 0.9rem;
line-height: 1.5;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px; border-radius: 8px;
background: #f8d7da; font-weight: 600;
max-width: 300px; font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
} }
.danger-zone h4 { .btn-primary {
color: #721c24; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
margin-top: 0; color: white;
margin-bottom: 0.5rem;
} }
.danger-zone p { .btn-primary:hover {
color: #721c24; transform: translateY(-2px);
font-size: 0.875rem; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
margin-bottom: 1rem;
} }
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn-danger:hover {
background: #b91c1c;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
}
/* Responsive Design */
@media (max-width: 768px) {
.edit-user-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.form-grid {
grid-template-columns: 1fr;
}
.danger-item {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
}
/* 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; }
.card:nth-child(4) { animation-delay: 0.4s; }
</style> </style>
<script> <script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,35 +1,51 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="settings-container">
<div class="header-section"> <!-- Header Section -->
<h1>⚙️ System Administrator Settings</h1> <div class="page-header">
<p class="subtitle">Global system configuration and management</p> <div class="header-content">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary"><i class="ti ti-arrow-left"></i> Back to Dashboard</a> <div class="header-left">
</div> <h1 class="page-title">
<span class="page-icon"><i class="ti ti-settings"></i></span>
<!-- System Statistics --> System Administrator Settings
<div class="stats-section"> </h1>
<h3>📊 System Overview</h3> <p class="page-subtitle">Global system configuration and management</p>
<div class="stats-grid">
<div class="stat-card">
<h4>{{ total_companies }}</h4>
<p>Total Companies</p>
</div> </div>
<div class="stat-card"> <div class="header-actions">
<h4>{{ total_users }}</h4> <a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<p>Total Users</p> <i class="ti ti-arrow-left"></i>
</div> Back to Dashboard
<div class="stat-card"> </a>
<h4>{{ total_system_admins }}</h4>
<p>System Administrators</p>
</div> </div>
</div> </div>
</div> </div>
<!-- System Statistics -->
<div class="stats-section">
<div class="stat-card">
<div class="stat-value">{{ total_companies }}</div>
<div class="stat-label">Total Companies</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ total_users }}</div>
<div class="stat-label">Total Users</div>
</div>
<div class="stat-card">
<div class="stat-value">{{ total_system_admins }}</div>
<div class="stat-label">System Administrators</div>
</div>
</div>
<!-- System Settings Form --> <!-- System Settings Form -->
<div class="settings-section"> <div class="card">
<h3>🔧 System Configuration</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-adjustments"></i></span>
System Configuration
</h2>
</div>
<div class="card-body">
<form method="POST" class="settings-form"> <form method="POST" class="settings-form">
<div class="setting-group"> <div class="setting-group">
<div class="setting-header"> <div class="setting-header">
@@ -111,11 +127,18 @@
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">Cancel</a> <a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">Cancel</a>
</div> </div>
</form> </form>
</div>
</div> </div>
<!-- System Information --> <!-- System Information -->
<div class="info-section"> <div class="card">
<h3> System Information</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-info-circle"></i></span>
System Information
</h2>
</div>
<div class="card-body">
<div class="info-grid"> <div class="info-grid">
<div class="info-card"> <div class="info-card">
<h4>Application Version</h4> <h4>Application Version</h4>
@@ -130,11 +153,17 @@
<p>Full system control</p> <p>Full system control</p>
</div> </div>
</div> </div>
</div>
</div> </div>
<!-- Danger Zone --> <!-- Danger Zone -->
<div class="danger-section"> <div class="danger-zone">
<h3>⚠️ Danger Zone</h3> <div class="danger-header">
<h2 class="danger-title">
<i class="ti ti-alert-triangle"></i>
Danger Zone
</h2>
</div>
<div class="danger-content"> <div class="danger-content">
<div class="danger-item"> <div class="danger-item">
<div class="danger-info"> <div class="danger-info">
@@ -164,8 +193,14 @@
</div> </div>
<!-- Quick Actions --> <!-- Quick Actions -->
<div class="actions-section"> <div class="card">
<h3>🚀 Quick Actions</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-rocket"></i></span>
Quick Actions
</h2>
</div>
<div class="card-body">
<div class="actions-grid"> <div class="actions-grid">
<a href="{{ url_for('users.system_admin_users') }}" class="action-card"> <a href="{{ url_for('users.system_admin_users') }}" class="action-card">
<div class="action-icon"><i class="ti ti-users"></i></div> <div class="action-icon"><i class="ti ti-users"></i></div>
@@ -176,7 +211,7 @@
</a> </a>
<a href="{{ url_for('system_admin.system_admin_companies') }}" class="action-card"> <a href="{{ url_for('system_admin.system_admin_companies') }}" class="action-card">
<div class="action-icon">🏢</div> <div class="action-icon"><i class="ti ti-building"></i></div>
<div class="action-content"> <div class="action-content">
<h4>Manage Companies</h4> <h4>Manage Companies</h4>
<p>View and manage all companies in the system</p> <p>View and manage all companies in the system</p>
@@ -184,77 +219,145 @@
</a> </a>
<a href="{{ url_for('system_admin.system_admin_time_entries') }}" class="action-card"> <a href="{{ url_for('system_admin.system_admin_time_entries') }}" class="action-card">
<div class="action-icon">⏱️</div> <div class="action-icon"><i class="ti ti-clock"></i></div>
<div class="action-content"> <div class="action-content">
<h4>View Time Entries</h4> <h4>View Time Entries</h4>
<p>Browse time tracking data across all companies</p> <p>Browse time tracking data across all companies</p>
</div> </div>
</a> </a>
</div> </div>
</div>
</div> </div>
</div> </div>
<style> <style>
.header-section { /* Container */
margin-bottom: 2rem; .settings-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
} }
.subtitle { /* Page Header */
color: #6c757d; .page-header {
margin-bottom: 1rem; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
} border-radius: 16px;
.stats-section {
background: #f8f9fa;
border-radius: 8px;
padding: 2rem; padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
} }
.stats-section h3 { .header-content {
margin-top: 0; display: flex;
margin-bottom: 1.5rem; justify-content: space-between;
color: #495057; align-items: center;
flex-wrap: wrap;
gap: 2rem;
} }
.stats-grid { .page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: rotate 8s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Stats Section */
.stats-section {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem; gap: 1.5rem;
margin-bottom: 2rem;
} }
.stat-card { .stat-card {
background: white; background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem; padding: 1.5rem;
border-radius: 12px;
text-align: center; text-align: center;
border: 1px solid #e5e7eb;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
} }
.stat-card h4 { .stat-card:hover {
font-size: 2rem; transform: translateY(-2px);
margin: 0 0 0.5rem 0; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
color: #007bff;
} }
.stat-card p { .stat-value {
margin: 0; font-size: 2.5rem;
color: #6c757d; font-weight: 700;
font-weight: 500; margin-bottom: 0.5rem;
color: #667eea;
} }
.settings-section { .stat-label {
font-size: 0.9rem;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
}
/* Cards */
.card {
background: white; background: white;
border: 1px solid #dee2e6; border-radius: 12px;
border-radius: 8px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
padding: 2rem; border: 1px solid #e5e7eb;
margin-bottom: 2rem; margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
} }
.settings-section h3 { .card:hover {
margin-top: 0; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
margin-bottom: 1.5rem; transform: translateY(-2px);
color: #495057; }
.card-header {
background: #f8f9fa;
padding: 1.5rem;
border-bottom: 1px solid #e5e7eb;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
}
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
} }
.settings-form { .settings-form {
@@ -268,8 +371,9 @@
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 2rem; gap: 2rem;
padding: 1.5rem; padding: 1.5rem;
border: 1px solid #e9ecef; border: 1px solid #e5e7eb;
border-radius: 8px; border-radius: 8px;
background: #f8f9fa;
} }
.setting-group.full-width { .setting-group.full-width {
@@ -295,7 +399,8 @@
.setting-header h4 { .setting-header h4 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: #495057; color: #1f2937;
font-weight: 600;
} }
.setting-header p { .setting-header p {
@@ -320,7 +425,7 @@
position: relative; position: relative;
width: 50px; width: 50px;
height: 24px; height: 24px;
background: #ccc; background: #e5e7eb;
border-radius: 24px; border-radius: 24px;
transition: background 0.3s; transition: background 0.3s;
} }
@@ -335,10 +440,11 @@
top: 2px; top: 2px;
left: 2px; left: 2px;
transition: transform 0.3s; transition: transform 0.3s;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider { .toggle-label input[type="checkbox"]:checked + .toggle-slider {
background: #007bff; background: #667eea;
} }
.toggle-label input[type="checkbox"]:checked + .toggle-slider::before { .toggle-label input[type="checkbox"]:checked + .toggle-slider::before {
@@ -347,7 +453,7 @@
.toggle-text { .toggle-text {
font-weight: 500; font-weight: 500;
color: #495057; color: #1f2937;
} }
.setting-description { .setting-description {
@@ -359,21 +465,9 @@
.form-actions { .form-actions {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
padding-top: 1rem; padding-top: 1.5rem;
border-top: 1px solid #e9ecef; border-top: 1px solid #e5e7eb;
} margin-top: 2rem;
.info-section {
background: #f8f9fa;
border-radius: 8px;
padding: 2rem;
margin-bottom: 2rem;
}
.info-section h3 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #495057;
} }
.info-grid { .info-grid {
@@ -383,41 +477,62 @@
} }
.info-card { .info-card {
background: white; background: #f8f9fa;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
border-radius: 8px; border-radius: 8px;
padding: 1.5rem; padding: 1.5rem;
} }
.info-card h4 { .info-card h4 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: #495057; color: #374151;
font-size: 1rem; font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600;
} }
.info-card p { .info-card p {
margin: 0; margin: 0;
color: #6c757d; color: #1f2937;
font-size: 1.1rem;
font-weight: 500;
} }
.danger-section { /* Danger Zone */
background: #f8d7da; .danger-zone {
border: 2px solid #dc3545; margin-top: 3rem;
border-radius: 8px; background: #fef2f2;
padding: 2rem; border: 2px solid #fecaca;
margin-bottom: 2rem; border-radius: 12px;
overflow: hidden;
} }
.danger-section h3 { .danger-header {
margin-top: 0; background: #fee2e2;
margin-bottom: 1.5rem; padding: 1.5rem;
color: #721c24; border-bottom: 1px solid #fecaca;
}
.danger-title {
font-size: 1.25rem;
font-weight: 600;
color: #991b1b;
margin: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.danger-title i {
font-size: 1.5rem;
} }
.danger-content { .danger-content {
padding: 2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1.5rem;
} }
.danger-item { .danger-item {
@@ -427,82 +542,145 @@
background: white; background: white;
padding: 1.5rem; padding: 1.5rem;
border-radius: 8px; border-radius: 8px;
border: 1px solid #dc3545; border: 1px solid #fecaca;
gap: 2rem;
} }
.danger-info h4 { .danger-info h4 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
color: #721c24; color: #991b1b;
font-weight: 600;
} }
.danger-info p { .danger-info p {
margin: 0; margin: 0;
color: #721c24; color: #7f1d1d;
font-size: 0.9rem; font-size: 0.9rem;
} line-height: 1.5;
.actions-section {
background: #f8f9fa;
border-radius: 8px;
padding: 2rem;
}
.actions-section h3 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #495057;
} }
.actions-grid { .actions-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem; gap: 1.5rem;
} }
.action-card { .action-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1.5rem;
display: flex; display: flex;
align-items: center; gap: 1.5rem;
gap: 1rem; padding: 1.5rem;
background: #f8f9fa;
border-radius: 8px;
text-decoration: none; text-decoration: none;
color: inherit; transition: all 0.2s ease;
transition: all 0.2s; border: 2px solid transparent;
align-items: center;
} }
.action-card:hover { .action-card:hover {
box-shadow: 0 2px 8px rgba(0,0,0,0.1); background: white;
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
text-decoration: none; text-decoration: none;
color: inherit;
} }
.action-icon { .action-icon {
font-size: 2rem; font-size: 2.5rem;
width: 60px; flex-shrink: 0;
height: 60px; }
display: flex;
align-items: center; .action-icon i {
justify-content: center; font-size: 2.5rem;
background: #f8f9fa; color: #667eea;
border-radius: 8px;
} }
.action-content h4 { .action-content h4 {
margin: 0 0 0.5rem 0; margin: 0 0 0.25rem 0;
color: #495057; color: #1f2937;
font-weight: 600;
font-size: 1.1rem;
} }
.action-content p { .action-content p {
margin: 0; margin: 0;
color: #6c757d; color: #6b7280;
font-size: 0.875rem; font-size: 0.9rem;
} }
/* Button styles now centralized in main style.css */ /* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn-danger:hover {
background: #b91c1c;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
}
.btn-warning {
background: #f59e0b;
color: white;
}
.btn-warning:hover {
background: #d97706;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
}
/* Responsive Design */
@media (max-width: 768px) { @media (max-width: 768px) {
.settings-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.setting-group { .setting-group {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
@@ -513,5 +691,26 @@
gap: 1rem; gap: 1rem;
} }
} }
/* 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> </style>
{% endblock %} {% endblock %}

View File

@@ -1,16 +1,35 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="time-entries-container">
<div class="header-section"> <!-- Header Section -->
<h1>⏱️ System Admin - Time Entries</h1> <div class="page-header">
<p class="subtitle">View time entries across all companies</p> <div class="header-content">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary"><i class="ti ti-arrow-left"></i> Back to Dashboard</a> <div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-clock"></i></span>
System Admin - Time Entries
</h1>
<p class="page-subtitle">View time entries across all companies</p>
</div>
<div class="header-actions">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Dashboard
</a>
</div>
</div>
</div> </div>
<!-- Filter Section --> <!-- Filter Section -->
<div class="filter-section"> <div class="card">
<h3>Filter Time Entries</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-filter"></i></span>
Filter Time Entries
</h2>
</div>
<div class="card-body">
<form method="GET" class="filter-form"> <form method="GET" class="filter-form">
<div class="filter-group"> <div class="filter-group">
<label for="company">Company:</label> <label for="company">Company:</label>
@@ -28,11 +47,13 @@
<a href="{{ url_for('system_admin.system_admin_time_entries') }}" class="btn btn-sm btn-outline">Clear Filter</a> <a href="{{ url_for('system_admin.system_admin_time_entries') }}" class="btn btn-sm btn-outline">Clear Filter</a>
{% endif %} {% endif %}
</form> </form>
</div>
</div> </div>
<!-- Time Entries Table --> <!-- Time Entries Table -->
{% if entries.items %} {% if entries.items %}
<div class="table-section"> <div class="card">
<div class="card-body no-padding">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@@ -105,6 +126,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
<!-- Pagination --> <!-- Pagination -->
@@ -152,55 +174,127 @@
<!-- Summary Statistics --> <!-- Summary Statistics -->
{% if entries.items %} {% if entries.items %}
<div class="summary-section"> <div class="stats-section">
<h3>📊 Summary Statistics</h3> <div class="stat-card">
<div class="summary-grid"> <div class="stat-value">{{ entries.total }}</div>
<div class="summary-card"> <div class="stat-label">Total Entries</div>
<h4>Total Entries</h4> </div>
<p class="summary-number">{{ entries.total }}</p> <div class="stat-card">
</div> <div class="stat-value">{{ entries.items | selectattr('0.departure_time', 'equalto', None) | list | length }}</div>
<div class="summary-card"> <div class="stat-label">Active Sessions</div>
<h4>Active Sessions</h4> </div>
<p class="summary-number">{{ entries.items | selectattr('0.departure_time', 'equalto', None) | list | length }}</p> <div class="stat-card">
</div> <div class="stat-value">{{ entries.items | selectattr('0.is_paused', 'equalto', True) | list | length }}</div>
<div class="summary-card"> <div class="stat-label">Paused Sessions</div>
<h4>Paused Sessions</h4> </div>
<p class="summary-number">{{ entries.items | selectattr('0.is_paused', 'equalto', True) | list | length }}</p> <div class="stat-card">
</div> <div class="stat-value">
<div class="summary-card"> {{ entries.items | selectattr('0.arrival_time') | selectattr('0.departure_time', 'defined') |
<h4>Completed Today</h4> list | length }}
<p class="summary-number">
{{ entries.items | selectattr('0.arrival_time') | selectattr('0.departure_time', 'defined') |
list | length }}
</p>
</div> </div>
<div class="stat-label">Completed Today</div>
</div> </div>
</div> </div>
{% endif %} {% endif %}
</div> </div>
<style> <style>
.header-section { /* Container */
.time-entries-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
} }
.subtitle { .header-content {
color: #6c757d; display: flex;
margin-bottom: 1rem; justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
} }
.filter-section { .page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
background: #f8f9fa; background: #f8f9fa;
padding: 1.5rem; padding: 1.5rem;
border-radius: 8px; border-bottom: 1px solid #e5e7eb;
margin-bottom: 2rem;
} }
.filter-section h3 { .card-title {
margin-top: 0; font-size: 1.25rem;
margin-bottom: 1rem; font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
.card-body.no-padding {
padding: 0;
}
/* Filter Form */
.filter-form { .filter-form {
display: flex; display: flex;
align-items: end; align-items: end;
@@ -215,24 +309,24 @@
} }
.filter-group label { .filter-group label {
font-weight: 500; font-weight: 600;
color: #495057; color: #374151;
font-size: 0.875rem;
} }
.form-control { .form-control {
padding: 0.5rem; padding: 0.625rem 1rem;
border: 1px solid #ced4da; border: 1px solid #e5e7eb;
border-radius: 4px; border-radius: 8px;
font-size: 1rem; font-size: 1rem;
min-width: 200px; min-width: 250px;
transition: all 0.2s ease;
} }
.table-section { .form-control:focus {
background: white; outline: none;
border-radius: 8px; border-color: #667eea;
overflow: hidden; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
margin-bottom: 2rem;
} }
.table { .table {
@@ -252,7 +346,10 @@
.table th { .table th {
background: #f8f9fa; background: #f8f9fa;
font-weight: 600; font-weight: 600;
color: #495057; color: #374151;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.8rem;
} }
.paused-entry { .paused-entry {
@@ -260,18 +357,18 @@
} }
.company-name { .company-name {
font-weight: 500; font-weight: 600;
color: #495057; color: #1f2937;
} }
.project-name { .project-name {
color: #007bff; color: #667eea;
font-weight: 500; font-weight: 600;
} }
.duration { .duration {
font-weight: 600; font-weight: 700;
color: #28a745; color: #10b981;
} }
.notes { .notes {
@@ -284,25 +381,26 @@
} }
.status-badge { .status-badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.status-active { .status-active {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
.status-paused { .status-paused {
background: #fff3cd; background: #fef3c7;
color: #856404; color: #92400e;
} }
.status-completed { .status-completed {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.pagination-section { .pagination-section {
@@ -320,21 +418,27 @@
} }
.page-link { .page-link {
padding: 0.5rem 0.75rem; padding: 0.5rem 1rem;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
color: #007bff; color: #667eea;
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
} }
.page-link:hover { .page-link:hover {
background: #e9ecef; background: #f3f4f6;
border-color: #667eea;
} }
.page-link.current { .page-link.current {
background: #007bff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
border-color: #007bff; border-color: transparent;
} }
.pagination-info { .pagination-info {
@@ -349,57 +453,130 @@
color: #6c757d; color: #6c757d;
} }
.summary-section { /* Stats Section */
background: #f8f9fa; .stats-section {
border-radius: 8px;
padding: 2rem;
}
.summary-section h3 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #495057;
}
.summary-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem; gap: 1.5rem;
margin-top: 2rem;
} }
.summary-card { .stat-card {
background: white; background: white;
border-radius: 8px;
padding: 1.5rem; padding: 1.5rem;
border-radius: 12px;
text-align: center; text-align: center;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
} }
.summary-card h4 { .stat-card:hover {
margin: 0 0 0.5rem 0; transform: translateY(-2px);
color: #6c757d; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
font-size: 0.875rem;
font-weight: 500;
} }
.summary-number { .stat-value {
font-size: 2rem; font-size: 2.5rem;
font-weight: 700;
margin-bottom: 0.5rem;
color: #667eea;
}
.stat-label {
font-size: 0.9rem;
color: #6b7280;
text-transform: uppercase;
letter-spacing: 0.5px;
font-weight: 600; font-weight: 600;
color: #007bff;
margin: 0;
} }
/* Button styles now centralized in main style.css */ /* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.btn-sm {
font-size: 0.875rem;
padding: 0.5rem 1rem;
}
.btn-outline { .btn-outline {
background: transparent; background: transparent;
color: #007bff; color: #667eea;
border: 1px solid #007bff; border: 2px solid #667eea;
} }
.btn-outline:hover { .btn-outline:hover {
background: #007bff; background: #667eea;
color: white; color: white;
} }
/* Responsive Design */
@media (max-width: 768px) {
.time-entries-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.table {
font-size: 0.8rem;
}
.table th,
.table td {
padding: 0.5rem;
}
}
/* 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> </style>
{% endblock %} {% endblock %}

View File

@@ -1,16 +1,35 @@
{% extends "layout.html" %} {% extends "layout.html" %}
{% block content %} {% block content %}
<div class="container"> <div class="users-admin-container">
<div class="header-section"> <!-- Header Section -->
<h1><i class="ti ti-users"></i> System Admin - All Users</h1> <div class="page-header">
<p class="subtitle">Manage users across all companies</p> <div class="header-content">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-md btn-secondary"><i class="ti ti-arrow-left"></i> Back to Dashboard</a> <div class="header-left">
<h1 class="page-title">
<span class="page-icon"><i class="ti ti-users"></i></span>
System Admin - All Users
</h1>
<p class="page-subtitle">Manage users across all companies</p>
</div>
<div class="header-actions">
<a href="{{ url_for('system_admin.system_admin_dashboard') }}" class="btn btn-secondary">
<i class="ti ti-arrow-left"></i>
Back to Dashboard
</a>
</div>
</div>
</div> </div>
<!-- Filter Options --> <!-- Filter Options -->
<div class="filter-section"> <div class="card">
<h3>Filter Users</h3> <div class="card-header">
<h2 class="card-title">
<span class="icon"><i class="ti ti-filter"></i></span>
Filter Users
</h2>
</div>
<div class="card-body">
<div class="filter-buttons"> <div class="filter-buttons">
<a href="{{ url_for('users.system_admin_users') }}" <a href="{{ url_for('users.system_admin_users') }}"
class="btn btn-filter {% if not current_filter %}active{% endif %}"> class="btn btn-filter {% if not current_filter %}active{% endif %}">
@@ -37,11 +56,13 @@
Freelancers Freelancers
</a> </a>
</div> </div>
</div>
</div> </div>
<!-- Users Table --> <!-- Users Table -->
{% if users.items %} {% if users.items %}
<div class="table-section"> <div class="card">
<div class="card-body no-padding">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@@ -117,6 +138,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
<!-- Pagination --> <!-- Pagination -->
@@ -160,60 +182,132 @@
</div> </div>
<style> <style>
.header-section { /* Container */
.users-admin-container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
/* Page Header */
.page-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
color: white;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
} }
.subtitle { .header-content {
color: #6c757d; display: flex;
margin-bottom: 1rem; justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 2rem;
} }
.filter-section { .page-title {
font-size: 2rem;
font-weight: 700;
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-icon {
font-size: 2.5rem;
display: inline-block;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.page-subtitle {
font-size: 1.1rem;
opacity: 0.9;
margin: 0.5rem 0 0 0;
}
/* Cards */
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
border: 1px solid #e5e7eb;
margin-bottom: 1.5rem;
overflow: hidden;
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
.card-header {
background: #f8f9fa; background: #f8f9fa;
padding: 1.5rem; padding: 1.5rem;
border-radius: 8px; border-bottom: 1px solid #e5e7eb;
margin-bottom: 2rem;
} }
.filter-section h3 { .card-title {
margin-top: 0; font-size: 1.25rem;
margin-bottom: 1rem; font-weight: 600;
margin: 0;
color: #1f2937;
display: flex;
align-items: center;
gap: 0.5rem;
} }
.card-title .icon {
font-size: 1.5rem;
}
.card-body {
padding: 1.5rem;
}
.card-body.no-padding {
padding: 0;
}
/* Filter Buttons */
.filter-buttons { .filter-buttons {
display: flex; display: flex;
gap: 0.5rem; gap: 0.75rem;
flex-wrap: wrap; flex-wrap: wrap;
} }
.btn-filter { .btn-filter {
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
background: white; background: white;
color: #495057; color: #6b7280;
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 8px;
transition: all 0.2s; font-weight: 500;
transition: all 0.2s ease;
} }
.btn-filter:hover { .btn-filter:hover {
background: #e9ecef; background: #f3f4f6;
border-color: #667eea;
color: #667eea;
} }
.btn-filter.active { .btn-filter.active {
background: #007bff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
border-color: #007bff; border-color: transparent;
}
.table-section {
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
} }
/* Table */
.table { .table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -224,120 +318,127 @@
.table td { .table td {
padding: 1rem; padding: 1rem;
text-align: left; text-align: left;
border-bottom: 1px solid #dee2e6; border-bottom: 1px solid #e5e7eb;
} }
.table th { .table th {
background: #f8f9fa; background: #f8f9fa;
font-weight: 600; font-weight: 600;
color: #495057; color: #374151;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: 0.8rem;
}
.table tr:hover {
background: #f8f9fa;
} }
.blocked-user { .blocked-user {
background-color: #f8d7da !important; background-color: #fef2f2 !important;
} }
.company-name {
font-weight: 600;
color: #1f2937;
}
/* Badges */
.badge { .badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.badge-self { .badge-self {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.badge-personal { .badge-personal {
background: #fff3cd; background: #fef3c7;
color: #856404; color: #92400e;
} }
.badge-company { .badge-company {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.badge-freelancer { .badge-freelancer {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
/* Role Badges */
.role-badge { .role-badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.role-team_member { .role-team_member {
background: #e2e3e5; background: #e5e7eb;
color: #495057; color: #374151;
} }
.role-team_leader { .role-team_leader {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
.role-supervisor { .role-supervisor {
background: #d1ecf1; background: #dbeafe;
color: #0c5460; color: #1e40af;
} }
.role-admin { .role-admin {
background: #fff3cd; background: #fef3c7;
color: #856404; color: #92400e;
} }
.role-system_admin { .role-system_admin {
background: #f1c0e8; background: #ede9fe;
color: #6a1b99; color: #5b21b6;
} }
/* Status Badges */
.status-badge { .status-badge {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.75rem;
border-radius: 4px; border-radius: 20px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 600;
text-transform: uppercase;
} }
.status-active { .status-active {
background: #d4edda; background: #d1fae5;
color: #155724; color: #065f46;
} }
.status-blocked { .status-blocked {
background: #f8d7da; background: #fee2e2;
color: #721c24; color: #991b1b;
} }
.status-unverified { .status-unverified {
background: #fff3cd; background: #fef3c7;
color: #856404; color: #92400e;
} }
/* Action Buttons */
.action-buttons { .action-buttons {
display: flex; display: flex;
gap: 0.5rem; gap: 0.5rem;
} }
.btn { /* Pagination */
display: inline-block;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
}
/* Button styles now centralized in main style.css */
.pagination-section { .pagination-section {
margin-top: 2rem; margin: 2rem 0;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
@@ -351,37 +452,156 @@
} }
.page-link { .page-link {
padding: 0.5rem 0.75rem; padding: 0.5rem 1rem;
border: 1px solid #dee2e6; border: 1px solid #e5e7eb;
color: #007bff; color: #667eea;
text-decoration: none; text-decoration: none;
border-radius: 4px; border-radius: 8px;
font-weight: 500;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.25rem;
} }
.page-link:hover { .page-link:hover {
background: #e9ecef; background: #f3f4f6;
border-color: #667eea;
} }
.page-link.current { .page-link.current {
background: #007bff; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; color: white;
border-color: #007bff; border-color: transparent;
} }
.pagination-info { .pagination-info {
color: #6c757d; color: #6b7280;
margin: 0; margin: 0;
font-size: 0.9rem; font-size: 0.9rem;
} }
/* Empty State */
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 4rem 2rem;
color: #6c757d; background: white;
border-radius: 12px;
border: 1px solid #e5e7eb;
} }
.company-name { .empty-state h3 {
font-weight: 500; font-size: 1.5rem;
color: #1f2937;
margin-bottom: 0.5rem;
} }
.empty-state p {
color: #6b7280;
font-size: 1.1rem;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 0.5rem;
text-decoration: none;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.btn-secondary {
background: white;
color: #667eea;
border: 2px solid rgba(255, 255, 255, 0.3);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.btn-danger {
background: #dc2626;
color: white;
}
.btn-danger:hover {
background: #b91c1c;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(220, 38, 38, 0.3);
}
.btn-sm {
font-size: 0.875rem;
padding: 0.5rem 1rem;
}
/* Responsive Design */
@media (max-width: 768px) {
.users-admin-container {
padding: 1rem;
}
.page-header {
padding: 1.5rem;
}
.header-content {
flex-direction: column;
text-align: center;
}
.table {
font-size: 0.8rem;
}
.table th,
.table td {
padding: 0.5rem;
}
.action-buttons {
flex-direction: column;
gap: 0.25rem;
}
}
/* 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> </style>
{% endblock %} {% endblock %}