diff --git a/frontend/js/components/workspace-switcher.js b/frontend/js/components/workspace-switcher.js index 968eefd..1c8d213 100644 --- a/frontend/js/components/workspace-switcher.js +++ b/frontend/js/components/workspace-switcher.js @@ -1,5 +1,18 @@ import { api } from '../api.js'; import { showToast } from './toast.js'; +import { t, tn } from '../i18n.js'; + +// Reusable resource-count formatter. Returns localized "1 device" / "N devices" +// / "No devices" based on n. Generic so the same shape can wire users / +// playlists / schedules counts later without refactor - caller supplies the +// i18n key bases. +// keyBase: e.g. 'switcher.devices_count' (looks up _one / _other variants via tn) +// zeroKey: e.g. 'switcher.no_devices' (direct lookup for n === 0) +function formatResourceCount(n, keyBase, zeroKey) { + if (n === undefined || n === null) return ''; + if (n === 0) return t(zeroKey); + return tn(keyBase, n); +} // Render the workspace switcher inside #workspaceSwitcher based on the // /api/auth/me response. Three modes: @@ -37,14 +50,21 @@ export function renderWorkspaceSwitcher(me) {
`; diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index f5633f2..24d7a1d 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -1105,4 +1105,10 @@ export default { 'add_display.windows': 'Windows', 'add_display.smart_tv_note': 'Smart TVs (LG/Samsung): open the built-in browser and navigate to/player',
'add_display.pair_btn': 'Pair Display',
+
+ // Workspace switcher (Phase 3 MVP). devices_count is the only count exposed
+ // today; matching pattern for users/playlists/etc. when those land later.
+ 'switcher.devices_count_one': '1 device',
+ 'switcher.devices_count_other': '{n} devices',
+ 'switcher.no_devices': 'No devices',
};
diff --git a/server/routes/auth.js b/server/routes/auth.js
index ce04777..c0db230 100644
--- a/server/routes/auth.js
+++ b/server/routes/auth.js
@@ -305,11 +305,16 @@ router.get('/me', requireAuth, resolveTenancy, (req, res) => {
// UI can render admin affordances (rename pencil etc.) only where the
// caller has permission. The server still enforces permission on the
// actual mutation routes regardless of this advisory flag.
+ // device_count: correlated subquery on workspaces.id. Equality fails on NULL
+ // so unclaimed pair-pool devices (workspace_id IS NULL) are correctly excluded.
+ // Microseconds per row at current scale (~37 rows worst case for platform_admin);
+ // not optimizing - revisit if the admin list grows past a few hundred workspaces.
const isPlatformAdmin = req.user.role === 'platform_admin' || req.user.role === 'superadmin';
const accessible = isPlatformAdmin
? db.prepare(`
SELECT w.id, w.name, w.organization_id, o.name AS organization_name,
- wm.role AS workspace_role, om.role AS org_role
+ wm.role AS workspace_role, om.role AS org_role,
+ (SELECT COUNT(*) FROM devices WHERE workspace_id = w.id) AS device_count
FROM workspaces w
JOIN organizations o ON o.id = w.organization_id
LEFT JOIN workspace_members wm ON wm.workspace_id = w.id AND wm.user_id = ?
@@ -318,7 +323,8 @@ router.get('/me', requireAuth, resolveTenancy, (req, res) => {
`).all(req.user.id, req.user.id)
: db.prepare(`
SELECT w.id, w.name, w.organization_id, o.name AS organization_name,
- wm.role AS workspace_role, om.role AS org_role
+ wm.role AS workspace_role, om.role AS org_role,
+ (SELECT COUNT(*) FROM devices WHERE workspace_id = w.id) AS device_count
FROM workspace_members wm
JOIN workspaces w ON w.id = wm.workspace_id
JOIN organizations o ON o.id = w.organization_id