Refactor Admin Dashboard and organize Counts module
This commit is contained in:
18
app.py
18
app.py
@@ -38,7 +38,7 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1)
|
|||||||
|
|
||||||
|
|
||||||
# 1. Define the version
|
# 1. Define the version
|
||||||
APP_VERSION = '0.13.0'
|
APP_VERSION = '0.13.2'
|
||||||
|
|
||||||
# 2. Inject it into all templates automatically
|
# 2. Inject it into all templates automatically
|
||||||
@app.context_processor
|
@app.context_processor
|
||||||
@@ -164,22 +164,6 @@ def admin_dashboard():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/staff-mode')
|
|
||||||
@login_required
|
|
||||||
def staff_mode():
|
|
||||||
"""Allow admin/owner to switch to staff view for scanning"""
|
|
||||||
# Show staff dashboard view regardless of role
|
|
||||||
active_sessions = query_db('''
|
|
||||||
SELECT session_id, session_name, session_type, created_timestamp
|
|
||||||
FROM CountSessions
|
|
||||||
WHERE status = 'active'
|
|
||||||
ORDER BY created_timestamp DESC
|
|
||||||
''')
|
|
||||||
|
|
||||||
return render_template('staff_dashboard.html', sessions=active_sessions, is_admin_mode=True)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ==================== PWA SUPPORT ROUTES ====================
|
# ==================== PWA SUPPORT ROUTES ====================
|
||||||
|
|
||||||
@app.route('/manifest.json')
|
@app.route('/manifest.json')
|
||||||
|
|||||||
Binary file not shown.
@@ -10,6 +10,50 @@ def get_active_session(session_id):
|
|||||||
return None
|
return None
|
||||||
return sess
|
return sess
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@counting_bp.route('/counts/admin')
|
||||||
|
@login_required
|
||||||
|
def admin_dashboard():
|
||||||
|
"""Admin dashboard for Counts module"""
|
||||||
|
# Security check: Ensure user is admin/owner
|
||||||
|
if session.get('role') not in ['owner', 'admin']:
|
||||||
|
flash('Access denied. Admin role required.', 'danger')
|
||||||
|
return redirect(url_for('counting.index'))
|
||||||
|
|
||||||
|
show_archived = request.args.get('show_archived', '0') == '1'
|
||||||
|
|
||||||
|
# This SQL was moved from app.py
|
||||||
|
if show_archived:
|
||||||
|
sessions_list = query_db('''
|
||||||
|
SELECT s.*, u.full_name as created_by_name,
|
||||||
|
COUNT(DISTINCT lc.location_count_id) as total_locations,
|
||||||
|
SUM(CASE WHEN lc.status = 'completed' THEN 1 ELSE 0 END) as completed_locations,
|
||||||
|
SUM(CASE WHEN lc.status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_locations
|
||||||
|
FROM CountSessions s
|
||||||
|
LEFT JOIN Users u ON s.created_by = u.user_id
|
||||||
|
LEFT JOIN LocationCounts lc ON s.session_id = lc.session_id
|
||||||
|
WHERE s.status IN ('active', 'archived')
|
||||||
|
GROUP BY s.session_id
|
||||||
|
ORDER BY s.status ASC, s.created_timestamp DESC
|
||||||
|
''')
|
||||||
|
else:
|
||||||
|
sessions_list = query_db('''
|
||||||
|
SELECT s.*, u.full_name as created_by_name,
|
||||||
|
COUNT(DISTINCT lc.location_count_id) as total_locations,
|
||||||
|
SUM(CASE WHEN lc.status = 'completed' THEN 1 ELSE 0 END) as completed_locations,
|
||||||
|
SUM(CASE WHEN lc.status = 'in_progress' THEN 1 ELSE 0 END) as in_progress_locations
|
||||||
|
FROM CountSessions s
|
||||||
|
LEFT JOIN Users u ON s.created_by = u.user_id
|
||||||
|
LEFT JOIN LocationCounts lc ON s.session_id = lc.session_id
|
||||||
|
WHERE s.status = 'active'
|
||||||
|
GROUP BY s.session_id
|
||||||
|
ORDER BY s.created_timestamp DESC
|
||||||
|
''')
|
||||||
|
|
||||||
|
return render_template('counts/admin_dashboard.html', sessions=sessions_list, show_archived=show_archived)
|
||||||
|
|
||||||
|
|
||||||
@counting_bp.route('/counts')
|
@counting_bp.route('/counts')
|
||||||
@login_required
|
@login_required
|
||||||
def index():
|
def index():
|
||||||
@@ -33,7 +77,7 @@ def index():
|
|||||||
ORDER BY created_timestamp DESC
|
ORDER BY created_timestamp DESC
|
||||||
''')
|
''')
|
||||||
|
|
||||||
return render_template('staff_dashboard.html', sessions=active_sessions)
|
return render_template('counts/staff_dashboard.html', sessions=active_sessions)
|
||||||
|
|
||||||
|
|
||||||
@counting_bp.route('/count/<int:session_id>')
|
@counting_bp.route('/count/<int:session_id>')
|
||||||
@@ -91,7 +135,7 @@ def my_counts(session_id):
|
|||||||
ORDER BY lc.end_timestamp DESC
|
ORDER BY lc.end_timestamp DESC
|
||||||
''', [session_id, session['user_id']])
|
''', [session_id, session['user_id']])
|
||||||
|
|
||||||
return render_template('my_counts.html',
|
return render_template('counts/my_counts.html',
|
||||||
count_session=sess,
|
count_session=sess,
|
||||||
active_bins=active_bins,
|
active_bins=active_bins,
|
||||||
completed_bins=completed_bins)
|
completed_bins=completed_bins)
|
||||||
@@ -219,7 +263,7 @@ def count_location(session_id, location_count_id):
|
|||||||
ORDER BY lot_number
|
ORDER BY lot_number
|
||||||
''', [session_id, location['location_name'], location_count_id])
|
''', [session_id, location['location_name'], location_count_id])
|
||||||
|
|
||||||
return render_template('count_location.html',
|
return render_template('counts/count_location.html',
|
||||||
session_id=session_id,
|
session_id=session_id,
|
||||||
location=location,
|
location=location,
|
||||||
scans=scans,
|
scans=scans,
|
||||||
|
|||||||
@@ -4,121 +4,28 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="dashboard-container">
|
<div class="dashboard-container">
|
||||||
<!-- Mode Selector -->
|
<div class="dashboard-header" style="margin-top: var(--space-lg);">
|
||||||
<div class="mode-selector">
|
<div class="header-left" style="display: flex; align-items: center; gap: var(--space-md);">
|
||||||
<a href="{{ url_for('home') }}" class="btn btn-secondary btn-sm">
|
<a href="{{ url_for('home') }}" class="btn btn-secondary btn-sm">
|
||||||
<i class="fa-solid fa-arrow-left"></i> Back to Home
|
<i class="fa-solid fa-arrow-left"></i> Back to Home
|
||||||
</a>
|
</a>
|
||||||
<button class="mode-btn mode-btn-active" data-href="{{ url_for('admin_dashboard') }}">
|
<h1 class="page-title" style="margin-bottom: 0;">Admin Dashboard</h1>
|
||||||
👔 Admin Console
|
|
||||||
</button>
|
|
||||||
<button class="mode-btn" data-href="{{ url_for('staff_mode') }}">
|
|
||||||
📦 Scanning Mode
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
document.querySelectorAll('.mode-selector button').forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
window.location.href = this.getAttribute('data-href');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="dashboard-header">
|
|
||||||
<div class="header-left">
|
|
||||||
<h1 class="page-title">Admin Dashboard</h1>
|
|
||||||
<label class="filter-toggle">
|
|
||||||
<input type="checkbox" id="showArchived" {% if show_archived %}checked{% endif %} onchange="toggleArchived()">
|
|
||||||
<span class="filter-label">Show Archived</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ url_for('sessions.create_session') }}" class="btn btn-primary">
|
|
||||||
<span class="btn-icon">+</span> New Session
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
function toggleArchived() {
|
|
||||||
const checked = document.getElementById('showArchived').checked;
|
|
||||||
window.location.href = '{{ url_for("admin_dashboard") }}' + (checked ? '?show_archived=1' : '');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Modules Section -->
|
|
||||||
<div class="modules-section">
|
<div class="modules-section">
|
||||||
<h2 class="section-title">Modules</h2>
|
<h2 class="section-title">Modules</h2>
|
||||||
<div class="modules-grid">
|
<div class="modules-grid">
|
||||||
<div class="module-card module-card-active">
|
<a href="{{ url_for('counting.admin_dashboard') }}" class="module-card">
|
||||||
<div class="module-icon">📋</div>
|
<div class="module-icon">📊</div> <h3 class="module-name">Counts</h3>
|
||||||
<h3 class="module-name">Counts</h3>
|
|
||||||
<p class="module-desc">Cycle counts & physical inventory</p>
|
<p class="module-desc">Cycle counts & physical inventory</p>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
<a href="{{ url_for('cons_sheets.admin_processes') }}" class="module-card module-card-link">
|
<a href="{{ url_for('cons_sheets.admin_processes') }}" class="module-card module-card-link">
|
||||||
<div class="module-icon">📝</div>
|
<div class="module-icon">📝</div> <h3 class="module-name">Consumption Sheets</h3>
|
||||||
<h3 class="module-name">Consumption Sheets</h3>
|
|
||||||
<p class="module-desc">Production consumption tracking</p>
|
<p class="module-desc">Production consumption tracking</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if sessions %}
|
|
||||||
<div class="sessions-grid">
|
|
||||||
{% for session in sessions %}
|
|
||||||
<div class="session-card {% if session.status == 'archived' %}session-archived{% endif %}">
|
|
||||||
<div class="session-card-header">
|
|
||||||
<h3 class="session-name">
|
|
||||||
{{ session.session_name }}
|
|
||||||
{% if session.status == 'archived' %}<span class="archived-badge">ARCHIVED</span>{% endif %}
|
|
||||||
</h3>
|
|
||||||
<span class="session-type-badge session-type-{{ session.session_type }}">
|
|
||||||
{{ 'Full Physical' if session.session_type == 'full_physical' else 'Cycle Count' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="session-stats">
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-value">{{ session.total_locations or 0 }}</div>
|
|
||||||
<div class="stat-label">Total Locations</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-value">{{ session.completed_locations or 0 }}</div>
|
|
||||||
<div class="stat-label">Completed</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item">
|
|
||||||
<div class="stat-value">{{ session.in_progress_locations or 0 }}</div>
|
|
||||||
<div class="stat-label">In Progress</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="session-meta">
|
|
||||||
<div class="meta-item">
|
|
||||||
<span class="meta-label">Created:</span>
|
|
||||||
<span class="meta-value">{{ session.created_timestamp[:16] }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="meta-item">
|
|
||||||
<span class="meta-label">By:</span>
|
|
||||||
<span class="meta-value">{{ session.created_by_name }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="session-actions">
|
|
||||||
<a href="{{ url_for('sessions.session_detail', session_id=session.session_id) }}" class="btn btn-secondary btn-block">
|
|
||||||
View Details
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<div class="empty-state">
|
|
||||||
<div class="empty-icon">📋</div>
|
|
||||||
<h2 class="empty-title">No Active Sessions</h2>
|
|
||||||
<p class="empty-text">Create a new count session to get started</p>
|
|
||||||
<a href="{{ url_for('sessions.create_session') }}" class="btn btn-primary">
|
|
||||||
Create First Session
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -4,18 +4,18 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="dashboard-container">
|
<div class="dashboard-container">
|
||||||
<!-- Back to Admin Dashboard -->
|
<div class="dashboard-header" style="margin-top: var(--space-lg);">
|
||||||
<div class="mode-selector">
|
<div class="header-left" style="display: flex; align-items: center; gap: var(--space-md);">
|
||||||
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary btn-sm">
|
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary btn-sm">
|
||||||
<i class="fa-solid fa-arrow-left"></i> Back to Admin
|
<i class="fa-solid fa-arrow-left"></i> Back to Admin
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="dashboard-header">
|
<div>
|
||||||
<div class="header-left">
|
<h1 class="page-title" style="margin-bottom: 0;">Consumption Sheets</h1>
|
||||||
<h1 class="page-title">Consumption Sheets</h1>
|
<p class="page-subtitle" style="margin-bottom: 0;">Manage process types and templates</p>
|
||||||
<p class="page-subtitle">Manage process types and templates</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{{ url_for('cons_sheets.create_process') }}" class="btn btn-primary">
|
<a href="{{ url_for('cons_sheets.create_process') }}" class="btn btn-primary">
|
||||||
<span class="btn-icon">+</span> New Process
|
<span class="btn-icon">+</span> New Process
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
102
templates/counts/admin_dashboard.html
Normal file
102
templates/counts/admin_dashboard.html
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Inventory Counts - ScanLook{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="dashboard-container">
|
||||||
|
<div class="dashboard-header">
|
||||||
|
<div class="header-left">
|
||||||
|
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary btn-sm" style="margin-right: var(--space-md);">
|
||||||
|
<i class="fa-solid fa-arrow-left"></i> Back to Admin
|
||||||
|
</a>
|
||||||
|
<div>
|
||||||
|
<h1 class="page-title">Inventory Counts</h1>
|
||||||
|
<p class="page-subtitle">Manage cycle counts and physical inventory</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header-right">
|
||||||
|
<label class="filter-toggle" style="margin-right: var(--space-lg);">
|
||||||
|
<input type="checkbox" id="showArchived" {% if show_archived %}checked{% endif %} onchange="toggleArchived()">
|
||||||
|
<span class="filter-label">Show Archived</span>
|
||||||
|
</label>
|
||||||
|
<a href="{{ url_for('sessions.create_session') }}" class="btn btn-primary">
|
||||||
|
<span class="btn-icon">+</span> New Session
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if sessions %}
|
||||||
|
<div class="sessions-grid">
|
||||||
|
{% for session in sessions %}
|
||||||
|
<div class="session-card {% if session.status == 'archived' %}session-archived{% endif %}">
|
||||||
|
<div class="session-card-header">
|
||||||
|
<h3 class="session-name">
|
||||||
|
{{ session.session_name }}
|
||||||
|
{% if session.status == 'archived' %}<span class="archived-badge">ARCHIVED</span>{% endif %}
|
||||||
|
</h3>
|
||||||
|
<span class="session-type-badge session-type-{{ session.session_type }}">
|
||||||
|
{{ 'Full Physical' if session.session_type == 'full_physical' else 'Cycle Count' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="session-stats">
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value">{{ session.total_locations or 0 }}</div>
|
||||||
|
<div class="stat-label">Total Locations</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value">{{ session.completed_locations or 0 }}</div>
|
||||||
|
<div class="stat-label">Completed</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-item">
|
||||||
|
<div class="stat-value">{{ session.in_progress_locations or 0 }}</div>
|
||||||
|
<div class="stat-label">In Progress</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="session-meta">
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">Created:</span>
|
||||||
|
<span class="meta-value">{{ session.created_timestamp[:16] }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-item">
|
||||||
|
<span class="meta-label">By:</span>
|
||||||
|
<span class="meta-value">{{ session.created_by_name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="session-actions">
|
||||||
|
<a href="{{ url_for('sessions.session_detail', session_id=session.session_id) }}" class="btn btn-secondary btn-block">
|
||||||
|
View Details
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="empty-state">
|
||||||
|
<div class="empty-icon">📋</div>
|
||||||
|
<h2 class="empty-title">No Active Sessions</h2>
|
||||||
|
<p class="empty-text">Create a new count session to get started</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleArchived() {
|
||||||
|
const checked = document.getElementById('showArchived').checked;
|
||||||
|
window.location.href = '{{ url_for("counting.admin_dashboard") }}' + (checked ? '?show_archived=1' : '');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.dashboard-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
.header-left { display: flex; align-items: center; }
|
||||||
|
.header-right { display: flex; align-items: center; }
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user