From 76c184fe912dec9a9a94b83d31d28a031825c837 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 7 Aug 2026 10:30:28 -0400 Subject: [PATCH] Give every notification type its own row on the board The shopfloor board grouped cards by display style alone, so every type set to grid landed inside the Recertification row and every carousel type inside Recognition's - under a heading naming somebody else's type. Setting Awareness to grid put awareness messages under "Recertification Required". Each type now gets a row of its own, titled by its own name, and rotation state is per row: two carousel rows advance on their own indexes instead of sharing one counter, and two grid rows page independently. For the other direction there is notificationtypes.boardcategory. Types sharing a category share one row under the category name, so Change, Awareness and Incident can sit together while Recognition and Recertification keep their own. Blank - the default - means a row of its own. The category is part of the grouping key along with the display style, since a category cannot merge a banner with a row of tiles. A card that names no employee now renders its message as the tile or card, rather than a placeholder face above a blank name, which is what a grid type like Awareness looked like before. The layout fingerprint that makes open kiosks reload now covers the category and the grace window, so a re-grouped board reaches screens that are already up. --- frontend/src/views/ShopfloorDashboard.vue | 1690 +++++++++-------- plugins/notifications/api/routes.py | 16 +- .../frontend/views/NotificationTypesList.vue | 23 +- .../migrations/versions/0004_boardcategory.py | 47 + plugins/notifications/models/notification.py | 9 +- tests/test_plugin_migrations.py | 5 +- tests/test_plugins/test_board_category.py | 117 ++ 7 files changed, 1081 insertions(+), 826 deletions(-) create mode 100644 plugins/notifications/migrations/versions/0004_boardcategory.py create mode 100644 tests/test_plugins/test_board_category.py diff --git a/frontend/src/views/ShopfloorDashboard.vue b/frontend/src/views/ShopfloorDashboard.vue index f98521f..4ce6bc7 100644 --- a/frontend/src/views/ShopfloorDashboard.vue +++ b/frontend/src/views/ShopfloorDashboard.vue @@ -1,818 +1,872 @@ - - - - - + + + + + diff --git a/plugins/notifications/api/routes.py b/plugins/notifications/api/routes.py index 844652e..cf9275b 100644 --- a/plugins/notifications/api/routes.py +++ b/plugins/notifications/api/routes.py @@ -157,6 +157,13 @@ def _apply_display_fields(t, data): if ds not in _DISPLAY_STYLES: return "displaystyle must be one of: %s" % ", ".join(_DISPLAY_STYLES) t.displaystyle = ds + if 'boardcategory' in data: + category = (data.get('boardcategory') or '').strip() + if len(category) > 50: + return "boardcategory must be 50 characters or fewer" + # Blank stores as NULL: "no category" is the absence of one, not the + # empty-string category that every uncategorised type would share. + t.boardcategory = category or None if 'gracewindowminutes' in data: raw = data.get('gracewindowminutes') raw = 0 if raw in (None, '') else raw @@ -178,11 +185,11 @@ def _config_version(): reach pages that are already open.""" types = NotificationType.query.order_by(NotificationType.notificationtypeid).all() parts = [ - "%s|%s|%s|%d|%d|%s|%s|%s|%d|%d" % ( + "%s|%s|%s|%d|%d|%s|%s|%s|%d|%d|%s" % ( t.notificationtypeid, t.typecolor, t.displaystyle, int(bool(t.splitperemployee)), int(bool(t.showemployeephoto)), t.expirymode, t.expirydays, t.expiryhour, int(bool(t.isactive)), - int(t.gracewindowminutes or 0), + int(t.gracewindowminutes or 0), t.boardcategory or '', ) for t in types ] @@ -779,8 +786,11 @@ def get_shopfloor_notifications(): 'resolved': is_resolved, 'typename': ntype.typename if ntype else None, 'typecolor': ntype.typecolor if ntype else None, - # Per-type display behavior the dashboard groups/renders by. + # Per-type display behavior the dashboard groups/renders by. A + # boardcategory puts several types in one row under that name; + # blank gives the type a row of its own. 'displaystyle': (ntype.displaystyle or 'standard') if ntype else 'standard', + 'boardcategory': (ntype.boardcategory or '') if ntype else '', } # Employee info (photo only when the type wants it) diff --git a/plugins/notifications/frontend/views/NotificationTypesList.vue b/plugins/notifications/frontend/views/NotificationTypesList.vue index 1303047..40e717d 100644 --- a/plugins/notifications/frontend/views/NotificationTypesList.vue +++ b/plugins/notifications/frontend/views/NotificationTypesList.vue @@ -32,7 +32,10 @@ {{ t.typecolor }} - {{ t.displaystyle || 'standard' }} + + {{ t.displaystyle || 'standard' }} + {{ t.boardcategory }} + split photo @@ -87,6 +90,17 @@ + +