feat: enhance project data handling with new Jinja filters and update UI labels for clarity
This commit is contained in:
@@ -2851,17 +2851,20 @@ async def upload_all_project_data(
|
||||
Determine the grouping key for a file path.
|
||||
Files inside Auto_####/Auto_Leq/ or Auto_####/Auto_Lp_01/ are collapsed
|
||||
up to their Auto_#### parent so they all land in the same session.
|
||||
Only folder components are examined (not the filename, which is parts[-1]).
|
||||
"""
|
||||
# Find the deepest Auto_#### component (case-insensitive)
|
||||
# Only look at folder components — exclude the filename (last part)
|
||||
folder_parts = parts[:-1]
|
||||
auto_idx = None
|
||||
for i, p in enumerate(parts):
|
||||
if p.lower().startswith("auto_") and not p.lower().startswith("auto_leq") and not p.lower().startswith("auto_lp"):
|
||||
for i, p in enumerate(folder_parts):
|
||||
p_lower = p.lower()
|
||||
if p_lower.startswith("auto_") and not p_lower.startswith("auto_leq") and not p_lower.startswith("auto_lp"):
|
||||
auto_idx = i
|
||||
if auto_idx is not None:
|
||||
# Group key = everything up to and including Auto_####
|
||||
return "/".join(parts[:auto_idx + 1])
|
||||
return "/".join(folder_parts[:auto_idx + 1])
|
||||
# Fallback: use the immediate parent folder
|
||||
return "/".join(parts[:-1]) if len(parts) > 1 else ""
|
||||
return "/".join(folder_parts) if folder_parts else ""
|
||||
|
||||
# --- Group files by session key ---
|
||||
groups: dict[str, list[tuple[str, bytes]]] = defaultdict(list)
|
||||
|
||||
@@ -5,6 +5,7 @@ All routers should import `templates` from this module to get consistent
|
||||
filter and global function registration.
|
||||
"""
|
||||
|
||||
import json as _json
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
# Import timezone utilities
|
||||
@@ -32,8 +33,38 @@ def jinja_timezone_abbr():
|
||||
# Create templates instance
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
def jinja_local_date(dt, fmt="%m-%d-%y"):
|
||||
"""Jinja filter: format a UTC datetime as a local date string (e.g. 02-19-26)."""
|
||||
return format_local_datetime(dt, fmt)
|
||||
|
||||
|
||||
def jinja_fromjson(s):
|
||||
"""Jinja filter: parse a JSON string into a dict (returns {} on failure)."""
|
||||
if not s:
|
||||
return {}
|
||||
try:
|
||||
return _json.loads(s)
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
def jinja_same_date(dt1, dt2) -> bool:
|
||||
"""Jinja global: True if two datetimes fall on the same local date."""
|
||||
if not dt1 or not dt2:
|
||||
return False
|
||||
try:
|
||||
d1 = format_local_datetime(dt1, "%Y-%m-%d")
|
||||
d2 = format_local_datetime(dt2, "%Y-%m-%d")
|
||||
return d1 == d2
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# Register Jinja filters and globals
|
||||
templates.env.filters["local_datetime"] = jinja_local_datetime
|
||||
templates.env.filters["local_time"] = jinja_local_time
|
||||
templates.env.filters["local_date"] = jinja_local_date
|
||||
templates.env.filters["fromjson"] = jinja_fromjson
|
||||
templates.env.globals["timezone_abbr"] = jinja_timezone_abbr
|
||||
templates.env.globals["get_user_timezone"] = get_user_timezone
|
||||
templates.env.globals["same_date"] = jinja_same_date
|
||||
|
||||
@@ -23,12 +23,26 @@
|
||||
<path d="M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"></path>
|
||||
</svg>
|
||||
<div>
|
||||
{% set meta = session.session_metadata|fromjson if session.session_metadata else {} %}
|
||||
{% set is_manual = meta.get('source') in ('manual_upload', 'bulk_upload') %}
|
||||
<div class="font-semibold text-gray-900 dark:text-white">
|
||||
{{ session.started_at|local_datetime if session.started_at else 'Unknown Date' }}
|
||||
{% if location %}{{ location.name }}{% else %}Unknown Location{% endif %}
|
||||
{% if session.started_at %}
|
||||
—
|
||||
{% if session.stopped_at and not same_date(session.started_at, session.stopped_at) %}
|
||||
{{ session.started_at|local_date }} to {{ session.stopped_at|local_date }}
|
||||
{% else %}
|
||||
{{ session.started_at|local_date }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{% if unit %}{{ unit.id }}{% else %}Unknown Unit{% endif %}
|
||||
{% if location %} @ {{ location.name }}{% endif %}
|
||||
{% if is_manual %}
|
||||
{% set store = meta.get('store_name') %}
|
||||
Manual upload{% if store %} — Store {{ store }}{% endif %}
|
||||
{% elif unit %}
|
||||
{{ unit.id }}
|
||||
{% endif %}
|
||||
<span class="mx-2">•</span>
|
||||
{{ files|length }} file{{ 's' if files|length != 1 else '' }}
|
||||
</div>
|
||||
|
||||
@@ -235,7 +235,7 @@
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"></path>
|
||||
</svg>
|
||||
Upload All
|
||||
Upload Days
|
||||
</button>
|
||||
<button onclick="htmx.trigger('#unified-files', 'refresh')"
|
||||
class="px-3 py-2 text-sm bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
|
||||
@@ -248,7 +248,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Upload All Panel -->
|
||||
<!-- Upload Days Panel -->
|
||||
<div id="upload-all-panel" class="hidden border-b border-gray-200 dark:border-gray-700">
|
||||
<div class="px-6 py-4 bg-gray-50 dark:bg-gray-800/50">
|
||||
<p class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Bulk Import — Select Folder</p>
|
||||
@@ -1575,7 +1575,7 @@ document.getElementById('schedule-modal')?.addEventListener('click', function(e)
|
||||
}
|
||||
});
|
||||
|
||||
// ── Upload All ───────────────────────────────────────────────────────────────
|
||||
// ── Upload Days ───────────────────────────────────────────────────────────────
|
||||
|
||||
function toggleUploadAll() {
|
||||
const panel = document.getElementById('upload-all-panel');
|
||||
|
||||
Reference in New Issue
Block a user