@gaia-ai/addon-gaia-ui 0.9.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/Component/footer-actions/index.d.ts +58 -0
- package/dist/src/Component/footer-actions/index.js +111 -0
- package/dist/src/Component/list-chrome/index.d.ts +19 -0
- package/dist/src/Component/list-chrome/index.js +61 -0
- package/dist/src/Component/markdown/index.d.ts +68 -0
- package/dist/src/Component/markdown/index.js +133 -0
- package/dist/src/Component/section-list/index.d.ts +115 -23
- package/dist/src/Component/section-list/index.js +120 -34
- package/dist/src/Component/top-tabs/index.d.ts +110 -7
- package/dist/src/Component/top-tabs/index.js +88 -9
- package/dist/src/Entity/collection.js +1 -1
- package/dist/src/Entity/conductor/Data/conductors.d.ts +4 -3
- package/dist/src/Entity/conductor/Data/conductors.js +76 -10
- package/dist/src/Entity/conductor/Data/own-conductor.js +7 -1
- package/dist/src/Entity/conductor/Screen/conductors.d.ts +26 -41
- package/dist/src/Entity/conductor/Screen/conductors.js +65 -123
- package/dist/src/Entity/project/Screen/project-picker.d.ts +20 -31
- package/dist/src/Entity/project/Screen/project-picker.js +48 -58
- package/dist/src/Entity/project/Screen/projects-screen.d.ts +4 -2
- package/dist/src/Entity/project/Screen/projects-screen.js +11 -8
- package/dist/src/Entity/run/Component/run-detail/index.js +15 -7
- package/dist/src/Entity/run/Data/query.d.ts +10 -0
- package/dist/src/Entity/run/Data/query.js +16 -1
- package/dist/src/Entity/run/Data/runs.d.ts +2 -0
- package/dist/src/Entity/run/Data/runs.js +19 -1
- package/dist/src/Entity/run/Screen/run-detail.d.ts +16 -23
- package/dist/src/Entity/run/Screen/run-detail.js +27 -34
- package/dist/src/Entity/run/Screen/runs.d.ts +7 -3
- package/dist/src/Entity/run/Screen/runs.js +31 -48
- package/dist/src/Entity/run/Screen/user-runs.d.ts +79 -0
- package/dist/src/Entity/run/Screen/user-runs.js +98 -0
- package/dist/src/Entity/statistics/Data/statistics.d.ts +111 -9
- package/dist/src/Entity/statistics/Data/statistics.js +121 -9
- package/dist/src/Entity/statistics/Screen/dashboard.d.ts +10 -2
- package/dist/src/Entity/statistics/Screen/dashboard.js +127 -36
- package/dist/src/Entity/team/Data/members.d.ts +30 -0
- package/dist/src/Entity/team/Data/members.js +83 -0
- package/dist/src/Entity/team/Data/team.d.ts +22 -0
- package/dist/src/Entity/team/Data/team.js +38 -0
- package/dist/src/Entity/team/Data/teams.d.ts +223 -0
- package/dist/src/Entity/team/Data/teams.js +267 -0
- package/dist/src/Entity/team/Screen/team-users.d.ts +76 -0
- package/dist/src/Entity/team/Screen/team-users.js +215 -0
- package/dist/src/Entity/team/Screen/teams-screen.d.ts +116 -0
- package/dist/src/Entity/team/Screen/teams-screen.js +349 -0
- package/dist/src/Entity/ticket/Component/comment-item/index.d.ts +14 -1
- package/dist/src/Entity/ticket/Component/comment-item/index.js +15 -1
- package/dist/src/Entity/ticket/Component/edit-form/index.d.ts +8 -3
- package/dist/src/Entity/ticket/Component/edit-form/index.js +4 -6
- package/dist/src/Entity/ticket/Component/sections/index.js +16 -1
- package/dist/src/Entity/ticket/Component/ticket-detail/index.d.ts +102 -13
- package/dist/src/Entity/ticket/Component/ticket-detail/index.js +440 -72
- package/dist/src/Entity/ticket/Data/detail.d.ts +19 -3
- package/dist/src/Entity/ticket/Data/detail.js +20 -4
- package/dist/src/Entity/ticket/Data/query.d.ts +7 -0
- package/dist/src/Entity/ticket/Data/query.js +18 -2
- package/dist/src/Entity/ticket/Data/tickets.d.ts +24 -0
- package/dist/src/Entity/ticket/Data/tickets.js +41 -1
- package/dist/src/Entity/ticket/Screen/create-ticket-form.d.ts +10 -4
- package/dist/src/Entity/ticket/Screen/create-ticket-form.js +37 -16
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/edit-assignment-form.js +16 -16
- package/dist/src/Entity/ticket/Screen/parent-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/parent-filter-form.js +14 -11
- package/dist/src/Entity/ticket/Screen/ticket-agents.d.ts +5 -2
- package/dist/src/Entity/ticket/Screen/ticket-agents.js +14 -14
- package/dist/src/Entity/ticket/Screen/ticket-detail.d.ts +89 -4
- package/dist/src/Entity/ticket/Screen/ticket-detail.js +295 -74
- package/dist/src/Entity/ticket/Screen/tickets.d.ts +66 -3
- package/dist/src/Entity/ticket/Screen/tickets.js +140 -76
- package/dist/src/Entity/ticket/Screen/user-tickets.d.ts +91 -0
- package/dist/src/Entity/ticket/Screen/user-tickets.js +94 -0
- package/dist/src/Entity/ticket/Screen/who-filter-form.d.ts +6 -4
- package/dist/src/Entity/ticket/Screen/who-filter-form.js +14 -11
- package/dist/src/Entity/transport.d.ts +14 -4
- package/dist/src/Entity/transport.js +2 -1
- package/dist/src/Entity/user/Data/users.d.ts +87 -0
- package/dist/src/Entity/user/Data/users.js +60 -0
- package/dist/src/Entity/user/Screen/user-overview.d.ts +33 -0
- package/dist/src/Entity/user/Screen/user-overview.js +159 -0
- package/dist/src/Entity/user/Screen/users.d.ts +39 -0
- package/dist/src/Entity/user/Screen/users.js +136 -0
- package/dist/src/Extension/contract.d.ts +58 -0
- package/dist/src/Extension/contract.js +1 -0
- package/dist/src/Extension/load.d.ts +10 -0
- package/dist/src/Extension/load.js +21 -0
- package/dist/src/Extension/store.d.ts +11 -0
- package/dist/src/Extension/store.js +44 -0
- package/dist/src/Kernel/chrome.d.ts +49 -12
- package/dist/src/Kernel/chrome.js +150 -57
- package/dist/src/Kernel/key-input.js +1 -1
- package/dist/src/Kernel/mount.d.ts +1 -1
- package/dist/src/Kernel/mount.js +1 -1
- package/dist/src/Kernel/regions.d.ts +64 -0
- package/dist/src/Kernel/regions.js +328 -0
- package/dist/src/Kernel/renderer.d.ts +25 -0
- package/dist/src/Kernel/renderer.js +142 -0
- package/dist/src/Kernel/splash.d.ts +27 -0
- package/dist/src/Kernel/splash.js +72 -0
- package/dist/src/Kernel/tui-kernel.js +139 -435
- package/dist/src/Routing/context.d.ts +5 -0
- package/dist/src/Routing/context.js +6 -0
- package/dist/src/Routing/registry.js +21 -2
- package/dist/src/Routing/router.js +5 -1
- package/dist/src/Routing/routes.js +19 -0
- package/dist/src/Screen/async-modal-screen.d.ts +26 -0
- package/dist/src/Screen/async-modal-screen.js +44 -0
- package/dist/src/Screen/async-screen.d.ts +32 -40
- package/dist/src/Screen/async-screen.js +45 -41
- package/dist/src/Screen/full-screen.d.ts +1 -0
- package/dist/src/Screen/full-screen.js +3 -0
- package/dist/src/Screen/list-screen.d.ts +7 -1
- package/dist/src/Screen/list-screen.js +17 -6
- package/dist/src/Screen/modal-screen.d.ts +6 -0
- package/dist/src/Screen/modal-screen.js +2 -0
- package/dist/src/Screen/screen.d.ts +42 -6
- package/dist/src/Screen/screen.js +2 -2
- package/dist/src/Widget/textarea.js +10 -14
- package/dist/src/Widget/types.d.ts +1 -1
- package/dist/src/launcher.d.ts +24 -26
- package/dist/src/launcher.js +11 -11
- package/dist/src/lib/format.js +1 -1
- package/dist/src/lib/markdown.d.ts +53 -0
- package/dist/src/lib/markdown.js +522 -0
- package/dist/src/lib/refresh-signal.d.ts +56 -0
- package/dist/src/lib/refresh-signal.js +85 -0
- package/dist/src/lib/theme.d.ts +36 -0
- package/dist/src/lib/theme.js +40 -0
- package/dist/src/lib/version.d.ts +5 -0
- package/dist/src/lib/version.js +46 -0
- package/dist/src/lib/vnode.d.ts +86 -63
- package/dist/src/lib/vnode.js +112 -85
- package/dist/src/types.d.ts +47 -12
- package/dist/src/ui.d.ts +2 -0
- package/dist/src/ui.js +1 -0
- package/package.json +4 -3
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { VNode } from '@opentui/core';
|
|
2
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
5
|
+
import { type UserListRow } from '../../user/Data/users.js';
|
|
6
|
+
/** The crew as rows, or the reason there are none. */
|
|
7
|
+
interface CrewList {
|
|
8
|
+
rows: UserListRow[];
|
|
9
|
+
/**
|
|
10
|
+
* Present only when the crew could not be ESTABLISHED — a refused membership
|
|
11
|
+
* read, a team that did not resolve, a refused user collection. Never set for
|
|
12
|
+
* a crew that is genuinely empty: that is `rows: []`, and the table's own
|
|
13
|
+
* `emptyMessage` says it.
|
|
14
|
+
*/
|
|
15
|
+
error?: string;
|
|
16
|
+
/**
|
|
17
|
+
* No crew was named at all, which is NOT a failure — see
|
|
18
|
+
* {@link TeamUsersScreen.load}. Kept apart from `error` so the two are not
|
|
19
|
+
* reported in the same words: one says a read went wrong, the other says
|
|
20
|
+
* nothing has been chosen yet.
|
|
21
|
+
*/
|
|
22
|
+
unscoped?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* ONE crew's people — the team level's `Users` tab.
|
|
26
|
+
*
|
|
27
|
+
* `fallback` points at the crews list: Escape lands where the operator came from,
|
|
28
|
+
* which after the ruling is Teams and no longer a root Users tab.
|
|
29
|
+
*/
|
|
30
|
+
export declare class TeamUsersScreen extends AsyncScreen<CrewList> {
|
|
31
|
+
readonly frameId = "team-users";
|
|
32
|
+
topTab(): {
|
|
33
|
+
readonly level: "team";
|
|
34
|
+
readonly tab: "users";
|
|
35
|
+
};
|
|
36
|
+
reload(context: ScreenContext): void;
|
|
37
|
+
/**
|
|
38
|
+
* `id: 'users'` — the id GAIA-377's user-level screens already fall back to.
|
|
39
|
+
*
|
|
40
|
+
* There is exactly ONE users list now and this is it, so it keeps the id rather
|
|
41
|
+
* than taking a new one and stranding three `fallback: { routeId: 'users' }`
|
|
42
|
+
* declarations on a route that no longer exists (`Router.navigate` throws
|
|
43
|
+
* `unknown route` for one of those, on Escape, with no history). Those three
|
|
44
|
+
* carry no `teamId`, which is what {@link TeamUsersScreen.load} answers for.
|
|
45
|
+
*/
|
|
46
|
+
static readonly route: {
|
|
47
|
+
id: string;
|
|
48
|
+
path: string;
|
|
49
|
+
tab: {
|
|
50
|
+
id: string;
|
|
51
|
+
label: string;
|
|
52
|
+
scope: string;
|
|
53
|
+
order: number;
|
|
54
|
+
};
|
|
55
|
+
fallback: {
|
|
56
|
+
routeId: string;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
readonly id: string;
|
|
60
|
+
private readonly cursor;
|
|
61
|
+
get selectedIndex(): number;
|
|
62
|
+
set selectedIndex(value: number);
|
|
63
|
+
get rows(): readonly UserListRow[];
|
|
64
|
+
/** Whether the ready value is a CREW at all, rather than a notice about one. */
|
|
65
|
+
private get drawable();
|
|
66
|
+
protected load(context: ScreenContext): Promise<CrewList>;
|
|
67
|
+
protected renderLoading(): string[];
|
|
68
|
+
protected renderReady(result: CrewList): VNode;
|
|
69
|
+
protected chrome(context: ScreenContext): FullScreenChrome;
|
|
70
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
71
|
+
activate(index: number, context: ScreenContext): boolean;
|
|
72
|
+
select(index: number, context: ScreenContext): boolean;
|
|
73
|
+
handleInput(input: string, context: ScreenContext): boolean;
|
|
74
|
+
reset(): void;
|
|
75
|
+
}
|
|
76
|
+
export {};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { actionItem } from '../../../Component/footer-actions/index.js';
|
|
2
|
+
import { sectionRowNode, } from '../../../Component/section-list/index.js';
|
|
3
|
+
import { ListCursor } from '../../../lib/cursor.js';
|
|
4
|
+
import { columnNode } from '../../../lib/vnode.js';
|
|
5
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
6
|
+
import { listRowIds, listTableTree } from '../../collection.js';
|
|
7
|
+
import { closedPerRun, formatClosedPerRun, loadStatistics, } from '../../statistics/Data/statistics.js';
|
|
8
|
+
import { listUsers, TEAM_MEMBER_TABLE, } from '../../user/Data/users.js';
|
|
9
|
+
import { listTeamMemberIds } from '../Data/members.js';
|
|
10
|
+
import { TeamsScreen } from './teams-screen.js';
|
|
11
|
+
// GAIA-391 AC4 (second round) — the crew's people, on the TEAM level.
|
|
12
|
+
//
|
|
13
|
+
// This is where GAIA-377's `users` root tab went, by operator ruling: a person is
|
|
14
|
+
// reached through the crew they are in (Teams → one crew → Users → one person),
|
|
15
|
+
// so the users LIST is team-scoped and the user LEVEL below it is untouched.
|
|
16
|
+
//
|
|
17
|
+
// Three reads, because the answer needs three things and no two of them come
|
|
18
|
+
// from one resource:
|
|
19
|
+
//
|
|
20
|
+
// - WHO is in the crew `og_membership` (`Data/members.ts`)
|
|
21
|
+
// - what those people are the `user` collection — labels and uids
|
|
22
|
+
// - what they have done the team-scoped statistics' `by_user`
|
|
23
|
+
//
|
|
24
|
+
// The composition lives HERE rather than in `Data/`, and that is the boundary
|
|
25
|
+
// rule rather than taste: `Entity/*/Data/` may not reach into another slice
|
|
26
|
+
// (`tests/boundary.test.ts` G2), and `user` and `statistics` are two other
|
|
27
|
+
// slices. A screen crossing slices is ordinary; a data module doing it would need
|
|
28
|
+
// two new `ALLOWED_CROSS_SLICE` entries.
|
|
29
|
+
/** Rows that are plain text, keyed under one name: a failure state. */
|
|
30
|
+
const plainRows = (key, lines) => lines.map((text, index) => ({ key: `${key}-${index}`, text, bold: false }));
|
|
31
|
+
/** What this screen says when it was opened without a crew to open. */
|
|
32
|
+
const NO_CREW = 'Open a crew from Teams to see who is in it.';
|
|
33
|
+
/** One member's row: identity from the collection, figures from the document. */
|
|
34
|
+
function rowOf(user, statistics) {
|
|
35
|
+
const monthOf = (rows) => (rows ?? []).find((row) => row.id === user.id)?.month ?? 0;
|
|
36
|
+
const tickets30d = statistics === null ? 0 : monthOf(statistics.tickets.byUser);
|
|
37
|
+
const runs30d = statistics === null ? 0 : monthOf(statistics.runs.byUser);
|
|
38
|
+
return {
|
|
39
|
+
id: user.id,
|
|
40
|
+
label: user.label,
|
|
41
|
+
tickets30d,
|
|
42
|
+
runs30d,
|
|
43
|
+
closedPerRun30d: formatClosedPerRun(closedPerRun({ day: 0, week: 0, month: tickets30d }, { day: 0, week: 0, month: runs30d }).month),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* ONE crew's people — the team level's `Users` tab.
|
|
48
|
+
*
|
|
49
|
+
* `fallback` points at the crews list: Escape lands where the operator came from,
|
|
50
|
+
* which after the ruling is Teams and no longer a root Users tab.
|
|
51
|
+
*/
|
|
52
|
+
export class TeamUsersScreen extends AsyncScreen {
|
|
53
|
+
frameId = 'team-users';
|
|
54
|
+
topTab() {
|
|
55
|
+
return { level: 'team', tab: 'users' };
|
|
56
|
+
}
|
|
57
|
+
reload(context) {
|
|
58
|
+
this.retry(context);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `id: 'users'` — the id GAIA-377's user-level screens already fall back to.
|
|
62
|
+
*
|
|
63
|
+
* There is exactly ONE users list now and this is it, so it keeps the id rather
|
|
64
|
+
* than taking a new one and stranding three `fallback: { routeId: 'users' }`
|
|
65
|
+
* declarations on a route that no longer exists (`Router.navigate` throws
|
|
66
|
+
* `unknown route` for one of those, on Escape, with no history). Those three
|
|
67
|
+
* carry no `teamId`, which is what {@link TeamUsersScreen.load} answers for.
|
|
68
|
+
*/
|
|
69
|
+
static route = {
|
|
70
|
+
id: 'users',
|
|
71
|
+
path: '/teams/:teamId/users',
|
|
72
|
+
tab: { id: 'users', label: 'Users', scope: 'team', order: 20 },
|
|
73
|
+
fallback: { routeId: TeamsScreen.route.id },
|
|
74
|
+
};
|
|
75
|
+
id = TeamUsersScreen.route.id;
|
|
76
|
+
cursor = new ListCursor();
|
|
77
|
+
get selectedIndex() {
|
|
78
|
+
return this.cursor.index;
|
|
79
|
+
}
|
|
80
|
+
set selectedIndex(value) {
|
|
81
|
+
this.cursor.index = value;
|
|
82
|
+
}
|
|
83
|
+
get rows() {
|
|
84
|
+
return this.drawable ? (this.readyValue?.rows ?? []) : [];
|
|
85
|
+
}
|
|
86
|
+
/** Whether the ready value is a CREW at all, rather than a notice about one. */
|
|
87
|
+
get drawable() {
|
|
88
|
+
const result = this.readyValue;
|
|
89
|
+
return (result !== undefined && result.error === undefined && !result.unscoped);
|
|
90
|
+
}
|
|
91
|
+
async load(context) {
|
|
92
|
+
if (!context.services)
|
|
93
|
+
throw new Error('Team users requires services');
|
|
94
|
+
const teamId = context.params?.teamId;
|
|
95
|
+
// Reached without a crew — the shape the user level's `fallback` produces,
|
|
96
|
+
// because a person's route carries `userId` and never a `teamId`. A notice
|
|
97
|
+
// rather than a throw: there is nothing wrong here, there is just no crew
|
|
98
|
+
// chosen yet, and the operator needs to be told where to choose one.
|
|
99
|
+
if (!teamId)
|
|
100
|
+
return { rows: [], unscoped: true };
|
|
101
|
+
const client = context.services.client;
|
|
102
|
+
const [members, users, statistics] = await Promise.all([
|
|
103
|
+
listTeamMemberIds(client, teamId),
|
|
104
|
+
listUsers(client),
|
|
105
|
+
loadStatistics(client, undefined, undefined, teamId),
|
|
106
|
+
]);
|
|
107
|
+
// Either failure leaves the crew unknown, and an unknown crew must not be
|
|
108
|
+
// drawn as an empty one: without the memberships there is nobody to keep,
|
|
109
|
+
// and without the collection there is nobody to name.
|
|
110
|
+
if (members.ids === null)
|
|
111
|
+
return {
|
|
112
|
+
rows: [],
|
|
113
|
+
// `listTeamMemberIds` always names a reason beside a null crew, but the
|
|
114
|
+
// type does not promise it — so a missing one becomes a stated unknown
|
|
115
|
+
// rather than a screen with a blank explanation.
|
|
116
|
+
error: members.error ?? 'the crew could not be established',
|
|
117
|
+
};
|
|
118
|
+
if (users.error !== undefined)
|
|
119
|
+
return { rows: [], error: users.error };
|
|
120
|
+
const crew = new Set(members.ids);
|
|
121
|
+
const figures = statistics.kind === 'ok' ? statistics.statistics : null;
|
|
122
|
+
return {
|
|
123
|
+
rows: users.rows
|
|
124
|
+
// Anonymous (0) and Drupal uid 1 are never operator rows — the rule the
|
|
125
|
+
// root list carried, kept where the list now lives.
|
|
126
|
+
.filter((user) => crew.has(user.id) && user.uid !== 0 && user.uid !== 1)
|
|
127
|
+
.map((user) => rowOf(user, figures)),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
renderLoading() {
|
|
131
|
+
return ['Loading crew…'];
|
|
132
|
+
}
|
|
133
|
+
renderReady(result) {
|
|
134
|
+
const notice = (key, lines) => columnNode('team-users', ...plainRows(key, lines).map((row) => sectionRowNode('team-users', row)));
|
|
135
|
+
if (result.unscoped)
|
|
136
|
+
return notice('team-users-unscoped', [NO_CREW]);
|
|
137
|
+
if (result.error !== undefined)
|
|
138
|
+
return notice('team-users-error', [
|
|
139
|
+
'Could not read the crew.',
|
|
140
|
+
'',
|
|
141
|
+
result.error,
|
|
142
|
+
]);
|
|
143
|
+
return listTableTree(TEAM_MEMBER_TABLE, result.rows, undefined, {
|
|
144
|
+
selectedIndex: this.selectedIndex,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
chrome(context) {
|
|
148
|
+
const rows = this.rows;
|
|
149
|
+
const result = this.readyValue;
|
|
150
|
+
const name = context.params?.teamName ?? context.params?.teamId ?? 'team';
|
|
151
|
+
if (result?.unscoped)
|
|
152
|
+
return {
|
|
153
|
+
selectable: 0,
|
|
154
|
+
selectedIndex: 0,
|
|
155
|
+
rowIds: [],
|
|
156
|
+
title: 'GAIA crew',
|
|
157
|
+
meta: 'no crew chosen',
|
|
158
|
+
};
|
|
159
|
+
if (result?.error !== undefined)
|
|
160
|
+
return {
|
|
161
|
+
selectable: 0,
|
|
162
|
+
selectedIndex: 0,
|
|
163
|
+
rowIds: [],
|
|
164
|
+
title: `${name} · users`,
|
|
165
|
+
meta: 'crew unavailable',
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
selectable: rows.length,
|
|
169
|
+
selectedIndex: this.selectedIndex,
|
|
170
|
+
rowIds: listRowIds(TEAM_MEMBER_TABLE, rows),
|
|
171
|
+
title: `${name} · users`,
|
|
172
|
+
meta: `${rows.length} member(s)`,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
actions() {
|
|
176
|
+
return [
|
|
177
|
+
actionItem('team-users-enter', 'Enter', 'open'),
|
|
178
|
+
actionItem('team-users-tab', 'Tab', 'tabs'),
|
|
179
|
+
];
|
|
180
|
+
}
|
|
181
|
+
activate(index, context) {
|
|
182
|
+
const row = this.rows[index];
|
|
183
|
+
if (!row || !context.navigate)
|
|
184
|
+
return false;
|
|
185
|
+
context.navigate('user.overview', {
|
|
186
|
+
userId: row.id,
|
|
187
|
+
userName: row.label,
|
|
188
|
+
});
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
select(index, context) {
|
|
192
|
+
if (index < 0 || index >= this.rows.length)
|
|
193
|
+
return false;
|
|
194
|
+
this.selectedIndex = index;
|
|
195
|
+
context.requestDraw();
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
handleInput(input, context) {
|
|
199
|
+
if (super.handleInput(input, context))
|
|
200
|
+
return true;
|
|
201
|
+
if (this.readyValue === undefined)
|
|
202
|
+
return false;
|
|
203
|
+
if (this.cursor.handle(input, this.rows.length)) {
|
|
204
|
+
context.requestDraw();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (input === 'enter')
|
|
208
|
+
return this.activate(this.selectedIndex, context);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
reset() {
|
|
212
|
+
this.cursor.reset();
|
|
213
|
+
super.reset();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { VNode } from '@opentui/core';
|
|
2
|
+
import { AsyncScreen } from '../../../Screen/async-screen.js';
|
|
3
|
+
import type { FullScreenChrome } from '../../../Screen/full-screen.js';
|
|
4
|
+
import type { ScreenContext } from '../../../Screen/screen.js';
|
|
5
|
+
import { type StatisticsResult } from '../../statistics/Data/statistics.js';
|
|
6
|
+
import { type ResolvedTeam } from '../Data/team.js';
|
|
7
|
+
import { type TeamListResult, type TeamListRow } from '../Data/teams.js';
|
|
8
|
+
/**
|
|
9
|
+
* The Teams list — a ROOT tab, which is what makes it reachable.
|
|
10
|
+
*
|
|
11
|
+
* Root rather than project level, by the argument GAIA-232 used for Conductors: a
|
|
12
|
+
* team cuts ACROSS customer projects (it is the level that does, per GAIA-321), so
|
|
13
|
+
* "which crews are there" must be answerable without first choosing a project.
|
|
14
|
+
*
|
|
15
|
+
* `order: 50` puts it last, after Users. People, then the crews they are in — and
|
|
16
|
+
* appending rather than inserting keeps every exact-bar assertion in the vitest
|
|
17
|
+
* and Bun suites reading one unchanged prefix.
|
|
18
|
+
*/
|
|
19
|
+
export declare class TeamsScreen extends AsyncScreen<TeamListResult> {
|
|
20
|
+
readonly frameId = "teams";
|
|
21
|
+
topTab(): {
|
|
22
|
+
readonly level: "root";
|
|
23
|
+
readonly tab: "teams";
|
|
24
|
+
};
|
|
25
|
+
reload(context: ScreenContext): void;
|
|
26
|
+
static readonly route: {
|
|
27
|
+
id: string;
|
|
28
|
+
path: string;
|
|
29
|
+
tab: {
|
|
30
|
+
id: string;
|
|
31
|
+
label: string;
|
|
32
|
+
scope: string;
|
|
33
|
+
order: number;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
readonly id: string;
|
|
37
|
+
private readonly cursor;
|
|
38
|
+
get selectedIndex(): number;
|
|
39
|
+
set selectedIndex(value: number);
|
|
40
|
+
saveState(): {
|
|
41
|
+
selectedIndex: number;
|
|
42
|
+
};
|
|
43
|
+
restoreState(token: unknown): void;
|
|
44
|
+
get rows(): readonly TeamListRow[];
|
|
45
|
+
protected load(context: ScreenContext): Promise<TeamListResult>;
|
|
46
|
+
protected renderLoading(): string[];
|
|
47
|
+
protected renderReady(result: TeamListResult): VNode;
|
|
48
|
+
protected chrome(context: ScreenContext): FullScreenChrome;
|
|
49
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
50
|
+
activate(index: number, context: ScreenContext): boolean;
|
|
51
|
+
select(index: number, context: ScreenContext): boolean;
|
|
52
|
+
handleInput(input: string, context: ScreenContext): boolean;
|
|
53
|
+
reset(): void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* What one team's page is made of: who the crew is, and what it did.
|
|
57
|
+
*
|
|
58
|
+
* Carried as one value because the screen draws both, and because keeping the
|
|
59
|
+
* resolved identity beside the figures is what stops the header from falling back
|
|
60
|
+
* to a uuid when only the statistics read failed.
|
|
61
|
+
*/
|
|
62
|
+
interface TeamOverview {
|
|
63
|
+
team: ResolvedTeam;
|
|
64
|
+
statistics: StatisticsResult;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* ONE team's report: the crew's identity plus the team-scoped statistics, read
|
|
68
|
+
* through the `?team_id=` the control plane has served since GAIA-321.
|
|
69
|
+
*
|
|
70
|
+
* `fallback` points at the list, so Escape from a deep link lands somewhere real
|
|
71
|
+
* rather than on the default route — the rule `UserOverviewScreen` follows.
|
|
72
|
+
*/
|
|
73
|
+
export declare class TeamOverviewScreen extends AsyncScreen<TeamOverview> {
|
|
74
|
+
readonly frameId = "team-overview";
|
|
75
|
+
/**
|
|
76
|
+
* GAIA-391 AC4 (second round) — the team level's landing tab.
|
|
77
|
+
*
|
|
78
|
+
* The earlier round declared no `tab` here and argued a team owns no LEVEL
|
|
79
|
+
* because "there is nothing under it to tab between". The operator's ruling is
|
|
80
|
+
* the opposite: the users level is under it, so the crew has a bar of its own
|
|
81
|
+
* (Overview · Users) and this screen is its first entry.
|
|
82
|
+
*/
|
|
83
|
+
topTab(): {
|
|
84
|
+
readonly level: "team";
|
|
85
|
+
readonly tab: "overview";
|
|
86
|
+
};
|
|
87
|
+
reload(context: ScreenContext): void;
|
|
88
|
+
static readonly route: {
|
|
89
|
+
id: string;
|
|
90
|
+
path: string;
|
|
91
|
+
tab: {
|
|
92
|
+
id: string;
|
|
93
|
+
label: string;
|
|
94
|
+
scope: string;
|
|
95
|
+
order: number;
|
|
96
|
+
};
|
|
97
|
+
fallback: {
|
|
98
|
+
routeId: string;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
readonly id: string;
|
|
102
|
+
/**
|
|
103
|
+
* The crew's identity AND its figures, read in parallel.
|
|
104
|
+
*
|
|
105
|
+
* Two reads because they answer two questions and fail independently: a
|
|
106
|
+
* control plane that cannot serve statistics still knows who is in the team,
|
|
107
|
+
* and the header must not fall back to a uuid because a count timed out.
|
|
108
|
+
*/
|
|
109
|
+
protected load(context: ScreenContext): Promise<TeamOverview>;
|
|
110
|
+
protected renderLoading(): string[];
|
|
111
|
+
private readyRows;
|
|
112
|
+
protected renderReady(result: TeamOverview): VNode;
|
|
113
|
+
protected chrome(context: ScreenContext): FullScreenChrome;
|
|
114
|
+
protected actions(): import("../../../Component/footer-actions/index.js").ActionItem[];
|
|
115
|
+
}
|
|
116
|
+
export {};
|