Task #183: clickable dependency counts on admin Apps/Services/Users

Turned the inline dependency-count badges on each admin row into
focusable, keyboard-accessible buttons that open a drill-in modal
listing the actual rows behind the count.

Backend (artifacts/api-server/src/routes):
  - apps.ts: GET /admin/apps/:id/dependents/{groups,restrictions,opens}
  - services.ts: GET /admin/services/:id/dependents/orders
  - users.ts: GET /admin/users/:id/dependents/{notes,orders,conversations,messages}
  All paginated (limit default 50, max 200; offset) returning
  {items, totalCount, limit, offset, nextOffset}. Restrictions joins
  the role names that include each required permission.

OpenAPI:
  - Added 8 path operations + 16 schemas (Item + Page) and re-ran
    `pnpm --filter @workspace/api-spec run codegen`.

Frontend (artifacts/tx-os/src/pages/admin.tsx):
  - New DependencyDrillIn component reuses DrillInShell (Escape +
    backdrop close, RTL/LTR safe) and the existing LoadMoreSection
    pagination pattern from AppOpensDrillIn.
  - Each count part in Apps, Services, and Users panels is now a
    <button> with a unique data-testid (e.g. app-counts-groups-213,
    user-counts-messages-5) and an aria-label that reads
    "View {count}".
  - AdminPage owns dependencyTarget state for Apps/Services counts;
    UsersPanel owns its own (it already encapsulates user list).

Translations:
  - Added admin.dependents.* (titles, subtitles, empty/error/load
    labels, conversation/order/message helper strings, order status
    enum) to en.json and ar.json.

Verification: - tx-os typecheck clean; api-server has only the pre-existing
    executive-meetings.ts errors (untouched).
  - e2e tested via runTest: login as admin, opened Apps panel,
    drilled into groups + opens (Load more grew 50→100), drilled into
    Tea orders, drilled into user 5 conversations and messages,
    switched to Arabic/RTL and re-opened the Groups drill-in to
    confirm Arabic rendering with no raw i18n keys.
Replit-Task-Id: fe96a05b-325f-4e1f-901b-3a2235fb24b5
This commit is contained in:
riyadhafraa
2026-05-01 07:32:11 +00:00
parent b8ffe460ef
commit c0e55752a3
11 changed files with 3246 additions and 15 deletions
@@ -912,6 +912,167 @@ export interface AdminAppOpensByUser {
opens: AppOpenByUserEntry[];
}
export interface AppDependentGroupItem {
id: number;
name: string;
/** @nullable */
descriptionAr: string | null;
/** @nullable */
descriptionEn: string | null;
}
export interface AppDependentGroupsPage {
items: AppDependentGroupItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface AppDependentRestrictionItem {
id: number;
name: string;
/** @nullable */
descriptionAr: string | null;
/** @nullable */
descriptionEn: string | null;
/** Names of roles currently holding this permission. */
roles: string[];
}
export interface AppDependentRestrictionsPage {
items: AppDependentRestrictionItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface AppDependentOpenItem {
id: number;
createdAt: string;
userId: number;
username: string;
/** @nullable */
displayNameAr: string | null;
/** @nullable */
displayNameEn: string | null;
/** @nullable */
avatarUrl: string | null;
}
export interface AppDependentOpensPage {
items: AppDependentOpenItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface ServiceDependentOrderItem {
id: number;
status: string;
/** @nullable */
notes: string | null;
createdAt: string;
userId: number;
username: string;
/** @nullable */
displayNameAr: string | null;
/** @nullable */
displayNameEn: string | null;
}
export interface ServiceDependentOrdersPage {
items: ServiceDependentOrderItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface UserDependentNoteItem {
id: number;
title: string;
isPinned: boolean;
isArchived: boolean;
updatedAt: string;
}
export interface UserDependentNotesPage {
items: UserDependentNoteItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface UserDependentOrderItem {
id: number;
status: string;
/** @nullable */
notes: string | null;
createdAt: string;
serviceId: number;
serviceNameAr: string;
serviceNameEn: string;
}
export interface UserDependentOrdersPage {
items: UserDependentOrderItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface UserDependentConversationItem {
id: number;
/** @nullable */
nameAr: string | null;
/** @nullable */
nameEn: string | null;
isGroup: boolean;
createdAt: string;
}
export interface UserDependentConversationsPage {
items: UserDependentConversationItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface UserDependentMessageItem {
id: number;
content: string;
kind: string;
createdAt: string;
conversationId: number;
/** @nullable */
conversationNameAr: string | null;
/** @nullable */
conversationNameEn: string | null;
conversationIsGroup: boolean;
}
export interface UserDependentMessagesPage {
items: UserDependentMessageItem[];
totalCount: number;
limit: number;
offset: number;
/** @nullable */
nextOffset: number | null;
}
export interface AuditLogActor {
id: number;
username: string;
@@ -1327,3 +1488,99 @@ Combines with `targetId` and the other filters.
*/
actorUserId?: number;
};
export type GetAdminAppDependentGroupsParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminAppDependentRestrictionsParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminAppDependentOpensParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminServiceDependentOrdersParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminUserDependentNotesParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminUserDependentOrdersParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminUserDependentConversationsParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
export type GetAdminUserDependentMessagesParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};
File diff suppressed because it is too large Load Diff