From 2ef58ded854fc8fdd78bd83560bad7c6f00a6453 Mon Sep 17 00:00:00 2001 From: A R R R Associates Date: Tue, 7 Jul 2026 18:54:37 +0530 Subject: [PATCH] Add PWA mobile attendance shortcut --- app/ui/app.py | 2 + app/ui/routes/auth.py | 21 +++- app/ui/routes/pwa.py | 126 +++++++++++++++++++++++ app/ui/static/pwa/icons/icon-192.png | Bin 0 -> 3764 bytes app/ui/static/pwa/icons/icon-512.png | Bin 0 -> 10693 bytes app/ui/static/pwa/icons/maskable-512.png | Bin 0 -> 9195 bytes app/ui/static/pwa/sw.js | 42 ++++++++ app/ui/templates/base/layout.html | 49 +++++++++ 8 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 app/ui/routes/pwa.py create mode 100644 app/ui/static/pwa/icons/icon-192.png create mode 100644 app/ui/static/pwa/icons/icon-512.png create mode 100644 app/ui/static/pwa/icons/maskable-512.png create mode 100644 app/ui/static/pwa/sw.js diff --git a/app/ui/app.py b/app/ui/app.py index a18273e..c61e61d 100644 --- a/app/ui/app.py +++ b/app/ui/app.py @@ -27,6 +27,7 @@ from app.modules.notice_cases.ui import router as notice_cases_router from app.modules.wizards.ui import router as wizards_ui_router from app.modules.system_admin_dashboard.ui import router as system_admin_dashboard_router from app.ui.routes.auth import router as auth_router +from app.ui.routes.pwa import router as pwa_router from app.modules.workspace_navigation.ui import router as workspace_navigation_router from app.modules.firm_admin_dashboard.ui import router as firm_admin_dashboard_router from app.modules.aqmm_dashboard.ui import router as aqmm_dashboard_router @@ -38,6 +39,7 @@ def mount_ui(app: FastAPI) -> None: app.mount("/storage", StaticFiles(directory="/app/data/storage"), name="storage") app.include_router(marketplace_public_router) app.include_router(auth_router) + app.include_router(pwa_router) app.include_router(firm_admin_dashboard_router) app.include_router(system_settings_router) app.include_router(email_integration_router) diff --git a/app/ui/routes/auth.py b/app/ui/routes/auth.py index 4263ef1..7232d53 100644 --- a/app/ui/routes/auth.py +++ b/app/ui/routes/auth.py @@ -1,4 +1,4 @@ -from __future__ import annotations +from __future__ import annotations from datetime import datetime, timezone, timedelta @@ -26,6 +26,21 @@ from app.modules.email_integration.services import send_auth_otp_email, send_pas router = APIRouter() +PENDING_POST_LOGIN_REDIRECT_KEY = "pending_post_login_redirect" +SAFE_POST_LOGIN_REDIRECTS = { + "/mobile/attendance", + "/employee/attendance", + "/employee/dashboard", +} + + +def _consume_safe_post_login_redirect(request: Request) -> str | None: + value = request.session.pop(PENDING_POST_LOGIN_REDIRECT_KEY, None) + value = (value or "").strip() + if value in SAFE_POST_LOGIN_REDIRECTS: + return value + return None + def _dev_otp_print_enabled() -> bool: settings = get_settings() @@ -459,9 +474,11 @@ def login_submit( if active_financial_year: request.session["active_financial_year"] = active_financial_year request.session["must_change_password"] = must_change_password - request.session["post_login_redirect"] = _post_login_redirect( + default_post_login_redirect = _post_login_redirect( must_change_password, permissions, roles ) + pending_post_login_redirect = None if must_change_password else _consume_safe_post_login_redirect(request) + request.session["post_login_redirect"] = pending_post_login_redirect or default_post_login_redirect if _otp_required(bs, roles): code = start_otp(request) diff --git a/app/ui/routes/pwa.py b/app/ui/routes/pwa.py new file mode 100644 index 0000000..fe0a283 --- /dev/null +++ b/app/ui/routes/pwa.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +from pathlib import Path + +from fastapi import APIRouter, Request +from fastapi.responses import FileResponse, JSONResponse, RedirectResponse + +from app.core.db.common import CommonSessionLocal +from app.core.security.session_auth import SESSION_USER_ID_KEY, get_current_user +from app.modules.core.rbac.permission_guard import require_permission + +router = APIRouter(tags=["pwa-ui"]) + +PWA_REDIRECT_SESSION_KEY = "pending_post_login_redirect" +MOBILE_ATTENDANCE_PATH = "/mobile/attendance" +PWA_STATIC_ROOT = Path("app/ui/static/pwa") + + +def _safe_mobile_redirect_path(value: str | None) -> str | None: + value = (value or "").strip() + if value in {MOBILE_ATTENDANCE_PATH, "/employee/attendance", "/employee/dashboard"}: + return value + return None + + +def _remember_mobile_attendance_redirect(request: Request) -> None: + request.session[PWA_REDIRECT_SESSION_KEY] = MOBILE_ATTENDANCE_PATH + + +@router.get("/manifest.webmanifest", include_in_schema=False) +def pwa_manifest(): + return JSONResponse( + { + "name": "ARRR ERP Attendance", + "short_name": "ARRR ERP", + "description": "Mobile attendance access for ARRR ERP staff users.", + "id": "/mobile/attendance", + "start_url": "/mobile/attendance", + "scope": "/", + "display": "standalone", + "orientation": "portrait", + "theme_color": "#1e3a8a", + "background_color": "#f8fafc", + "categories": ["business", "productivity"], + "icons": [ + { + "src": "/static/pwa/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any", + }, + { + "src": "/static/pwa/icons/icon-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "any", + }, + { + "src": "/static/pwa/icons/maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable", + }, + ], + "shortcuts": [ + { + "name": "Mark Attendance", + "short_name": "Attendance", + "description": "Open mobile attendance punch screen.", + "url": MOBILE_ATTENDANCE_PATH, + "icons": [ + { + "src": "/static/pwa/icons/icon-192.png", + "sizes": "192x192", + "type": "image/png", + } + ], + } + ], + }, + media_type="application/manifest+json", + headers={"Cache-Control": "no-cache"}, + ) + + +@router.get("/sw.js", include_in_schema=False) +def pwa_service_worker(): + return FileResponse( + PWA_STATIC_ROOT / "sw.js", + media_type="application/javascript", + headers={ + "Cache-Control": "no-cache", + "Service-Worker-Allowed": "/", + }, + ) + + +@router.get("/mobile", include_in_schema=False) +def mobile_root(request: Request): + return RedirectResponse(url=MOBILE_ATTENDANCE_PATH, status_code=303) + + +@router.get(MOBILE_ATTENDANCE_PATH, include_in_schema=False) +def mobile_attendance(request: Request): + if not request.session.get(SESSION_USER_ID_KEY): + _remember_mobile_attendance_redirect(request) + return RedirectResponse(url="/login", status_code=303) + + if not request.session.get("otp_verified", False): + _remember_mobile_attendance_redirect(request) + return RedirectResponse(url="/otp", status_code=303) + + db = CommonSessionLocal() + try: + current_user = get_current_user(request, db=db) + if not current_user: + request.session.clear() + _remember_mobile_attendance_redirect(request) + return RedirectResponse(url="/login", status_code=303) + try: + require_permission(db, current_user, "employees.attendance.view_self") + except Exception: + return RedirectResponse(url="/employee/dashboard", status_code=303) + return RedirectResponse(url="/employee/attendance", status_code=303) + finally: + db.close() diff --git a/app/ui/static/pwa/icons/icon-192.png b/app/ui/static/pwa/icons/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..7d3206d4e5218991c7b658d3d88930e71850f73a GIT binary patch literal 3764 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 literal 0 HcmV?d00001 diff --git a/app/ui/static/pwa/icons/icon-512.png b/app/ui/static/pwa/icons/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..5f1fd69f4e6f1e038d167a7107820c7eb50bd57f GIT binary patch literal 10693 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 JU(Wp5{{c@WzOn!S literal 0 HcmV?d00001 diff --git a/app/ui/static/pwa/icons/maskable-512.png b/app/ui/static/pwa/icons/maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..5cf735f9378933142bd74c3f0d60cf7b90c99fef GIT binary patch literal 9195 zcmeHti8qw(|NlLMp(rHT8WhoLFS{8}*(zkO7(})bkBps}k>`n&C7zmOEn6s(eJrCa zlk8h#R}{t)W1TT(=6B2UJ-^TI5BQz){hsr6j^jS#+V0o;^?qIN>w3+jYi33V_KWWa z0N{X$@g++DfP#-ufCmQtSq~oA1^}54CYSWBgVUDA!`{0MqU2X5^=8My7$$2ES3G$V z9{z&;C1BhbiRLl!bWt+Nw$gKV$$MKccL}cQe%9k?z#X+y6`$rlH}_duO^;ew!4Adk zljW2>WMZs#yhlJTvB&Vx1iC+BS=Xam$~1bWcf1OGT≧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 literal 0 HcmV?d00001 diff --git a/app/ui/static/pwa/sw.js b/app/ui/static/pwa/sw.js new file mode 100644 index 0000000..922d6b9 --- /dev/null +++ b/app/ui/static/pwa/sw.js @@ -0,0 +1,42 @@ +const CACHE_VERSION = 'arrr-erp-pwa-v1'; +const CORE_ASSETS = [ + '/manifest.webmanifest', + '/static/css/theme_tokens.css', + '/static/pwa/icons/icon-192.png', + '/static/pwa/icons/icon-512.png' +]; + +self.addEventListener('install', (event) => { + event.waitUntil( + caches.open(CACHE_VERSION).then((cache) => cache.addAll(CORE_ASSETS)).catch(() => undefined) + ); + self.skipWaiting(); +}); + +self.addEventListener('activate', (event) => { + event.waitUntil( + caches.keys().then((keys) => Promise.all(keys.filter((key) => key !== CACHE_VERSION).map((key) => caches.delete(key)))) + ); + self.clients.claim(); +}); + +self.addEventListener('fetch', (event) => { + const request = event.request; + if (request.method !== 'GET') return; + + const url = new URL(request.url); + if (url.origin !== self.location.origin) return; + + if (url.pathname.startsWith('/static/')) { + event.respondWith( + caches.match(request).then((cached) => cached || fetch(request).then((response) => { + const copy = response.clone(); + caches.open(CACHE_VERSION).then((cache) => cache.put(request, copy)).catch(() => undefined); + return response; + })) + ); + return; + } + + event.respondWith(fetch(request).catch(() => caches.match('/static/css/theme_tokens.css'))); +}); diff --git a/app/ui/templates/base/layout.html b/app/ui/templates/base/layout.html index 87163d3..8f2acb1 100644 --- a/app/ui/templates/base/layout.html +++ b/app/ui/templates/base/layout.html @@ -6,6 +6,12 @@ {% set __title_user = current_user if current_user is defined else None %} {% set __firm_branding = get_current_firm_branding(request, __title_user) %} + + + + + + {% if __firm_branding.favicon_url %}{% endif %} {% set __title_auth = __title_user and request.session.get("otp_verified", False) %} {% if __title_auth %} @@ -502,6 +508,17 @@ {{ flash }} {% endif %} + {% if current_path == '/employee/attendance' or current_path == '/mobile/attendance' %} + + {% endif %} {% block content %}{% endblock %} @@ -627,5 +644,37 @@ {% endif %} + +