@skillcdn/console 0.3.2 → 0.4.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/README.md +3 -3
- package/dist/api.d.ts +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +1 -1
- package/dist/api.js.map +1 -1
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +96 -7
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/format.js +6 -4
- package/dist/cli/format.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +48 -17
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/pack.d.ts +7 -2
- package/dist/cli/pack.d.ts.map +1 -1
- package/dist/cli/pack.js +16 -2
- package/dist/cli/pack.js.map +1 -1
- package/dist/client.d.ts +7 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +7 -1
- package/dist/client.js.map +1 -1
- package/dist/compat.d.ts +1 -1
- package/dist/compat.js +2 -2
- package/dist/components/board.d.ts +12 -1
- package/dist/components/board.d.ts.map +1 -1
- package/dist/components/board.js +52 -4
- package/dist/components/board.js.map +1 -1
- package/dist/components/calendar.d.ts +4 -0
- package/dist/components/calendar.d.ts.map +1 -1
- package/dist/components/calendar.js +61 -22
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/decision-form.d.ts +12 -4
- package/dist/components/decision-form.d.ts.map +1 -1
- package/dist/components/decision-form.js +24 -6
- package/dist/components/decision-form.js.map +1 -1
- package/dist/components/decision-list.d.ts +4 -0
- package/dist/components/decision-list.d.ts.map +1 -1
- package/dist/components/decision-list.js +9 -6
- package/dist/components/decision-list.js.map +1 -1
- package/dist/components/documents.d.ts.map +1 -1
- package/dist/components/documents.js +3 -3
- package/dist/components/documents.js.map +1 -1
- package/dist/components/event-feed.d.ts.map +1 -1
- package/dist/components/event-feed.js +63 -8
- package/dist/components/event-feed.js.map +1 -1
- package/dist/components/my-decisions.d.ts +20 -0
- package/dist/components/my-decisions.d.ts.map +1 -0
- package/dist/components/my-decisions.js +36 -0
- package/dist/components/my-decisions.js.map +1 -0
- package/dist/components/people.d.ts.map +1 -1
- package/dist/components/people.js +1 -1
- package/dist/components/people.js.map +1 -1
- package/dist/components/projects.d.ts.map +1 -1
- package/dist/components/projects.js +3 -3
- package/dist/components/projects.js.map +1 -1
- package/dist/components/roadmap.js +1 -1
- package/dist/components/roadmap.js.map +1 -1
- package/dist/components/runs.d.ts.map +1 -1
- package/dist/components/runs.js +1 -1
- package/dist/components/runs.js.map +1 -1
- package/dist/components/shell.d.ts.map +1 -1
- package/dist/components/shell.js +36 -1
- package/dist/components/shell.js.map +1 -1
- package/dist/components/task-list.js +1 -1
- package/dist/components/task-list.js.map +1 -1
- package/dist/components/task-view.d.ts.map +1 -1
- package/dist/components/task-view.js +2 -2
- package/dist/components/task-view.js.map +1 -1
- package/dist/components/ui.d.ts +10 -0
- package/dist/components/ui.d.ts.map +1 -1
- package/dist/components/ui.js +15 -11
- package/dist/components/ui.js.map +1 -1
- package/dist/console.css +1642 -433
- package/dist/console.d.ts.map +1 -1
- package/dist/console.js +70 -7
- package/dist/console.js.map +1 -1
- package/dist/data.d.ts +11 -1
- package/dist/data.d.ts.map +1 -1
- package/dist/data.js +2 -0
- package/dist/data.js.map +1 -1
- package/dist/i18n/en.d.ts +34 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +36 -2
- package/dist/i18n/en.js.map +1 -1
- package/dist/i18n/ko.d.ts.map +1 -1
- package/dist/i18n/ko.js +37 -3
- package/dist/i18n/ko.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/router.d.ts +9 -1
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +12 -0
- package/dist/router.js.map +1 -1
- package/dist/routes.d.ts +3 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +3 -1
- package/dist/routes.js.map +1 -1
- package/dist/rules.js +1 -1
- package/dist/rules.js.map +1 -1
- package/dist/schemas.d.ts +242 -3
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +18 -4
- package/dist/schemas.js.map +1 -1
- package/docs/specs/cli.md +6 -5
- package/docs/specs/rest.md +11 -6
- package/package.json +2 -2
- package/skills/building-a-console/SKILL.md +3 -3
- package/skills/building-a-console/references/the-default-console.md +4 -4
- package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
- package/skills/working-the-board/SKILL.md +6 -4
- package/skills/working-the-board/references/terms.md +1 -1
- package/src/api.ts +2 -0
- package/src/cli/cli.ts +123 -7
- package/src/cli/format.ts +11 -4
- package/src/cli/help.ts +49 -16
- package/src/cli/pack.ts +17 -1
- package/src/client.ts +21 -1
- package/src/compat.ts +2 -2
- package/src/components/board.tsx +114 -21
- package/src/components/calendar.tsx +207 -102
- package/src/components/decision-form.tsx +71 -23
- package/src/components/decision-list.tsx +30 -3
- package/src/components/documents.tsx +27 -11
- package/src/components/event-feed.tsx +116 -33
- package/src/components/my-decisions.tsx +138 -0
- package/src/components/people.tsx +3 -1
- package/src/components/projects.tsx +7 -5
- package/src/components/roadmap.tsx +1 -1
- package/src/components/runs.tsx +5 -1
- package/src/components/shell.tsx +36 -1
- package/src/components/task-list.tsx +1 -1
- package/src/components/task-view.tsx +48 -47
- package/src/components/ui.tsx +64 -11
- package/src/console.tsx +129 -27
- package/src/data.ts +17 -0
- package/src/i18n/en.ts +38 -2
- package/src/i18n/ko.ts +39 -3
- package/src/index.ts +3 -0
- package/src/router.ts +18 -1
- package/src/routes.ts +3 -1
- package/src/rules.ts +1 -1
- package/src/schemas.ts +20 -4
- package/src/styles/console.css +1642 -433
package/src/components/ui.tsx
CHANGED
|
@@ -43,11 +43,23 @@ export function Button(
|
|
|
43
43
|
|
|
44
44
|
export function Badge(props: {
|
|
45
45
|
readonly tone?: "neutral" | "accent" | "point" | "success" | "warning" | "danger";
|
|
46
|
+
/** What the badge says, which the stylesheet shows in its shape: a state carries a dot, a priority is outlined, a due day is a square-cornered tag, a count is figures. */
|
|
47
|
+
readonly kind?: "state" | "priority" | "due" | "count" | undefined;
|
|
48
|
+
/** A class of the badge's own beside the tone's and the kind's, for a step within a kind. */
|
|
49
|
+
readonly className?: string | undefined;
|
|
46
50
|
readonly title?: string;
|
|
47
51
|
readonly children: ReactNode;
|
|
48
52
|
}) {
|
|
49
53
|
return (
|
|
50
|
-
<span
|
|
54
|
+
<span
|
|
55
|
+
className={cx(
|
|
56
|
+
"sc-badge",
|
|
57
|
+
`sc-badge-${props.tone ?? "neutral"}`,
|
|
58
|
+
props.kind !== undefined && `sc-badge-kind-${props.kind}`,
|
|
59
|
+
props.className,
|
|
60
|
+
)}
|
|
61
|
+
title={props.title}
|
|
62
|
+
>
|
|
51
63
|
{props.children}
|
|
52
64
|
</span>
|
|
53
65
|
);
|
|
@@ -195,7 +207,7 @@ export const ROLE_LABELS: Readonly<Record<PersonRole, string>> = {
|
|
|
195
207
|
export function RoleBadge(props: { readonly role: PersonRole }) {
|
|
196
208
|
const words = useWords();
|
|
197
209
|
return (
|
|
198
|
-
<Badge tone={props.role === "admin" ? "
|
|
210
|
+
<Badge tone={props.role === "admin" ? "accent" : "neutral"}>
|
|
199
211
|
{words.vocabulary.role[props.role]}
|
|
200
212
|
</Badge>
|
|
201
213
|
);
|
|
@@ -211,7 +223,11 @@ export function StateBadge(props: { readonly state: TaskState }) {
|
|
|
211
223
|
: props.state === "in_progress" || props.state === "in_review"
|
|
212
224
|
? "accent"
|
|
213
225
|
: "neutral";
|
|
214
|
-
return
|
|
226
|
+
return (
|
|
227
|
+
<Badge tone={tone} kind="state">
|
|
228
|
+
{words.vocabulary.state[props.state]}
|
|
229
|
+
</Badge>
|
|
230
|
+
);
|
|
215
231
|
}
|
|
216
232
|
|
|
217
233
|
export function PriorityBadge(props: { readonly priority: TaskPriority }) {
|
|
@@ -221,7 +237,11 @@ export function PriorityBadge(props: { readonly priority: TaskPriority }) {
|
|
|
221
237
|
}
|
|
222
238
|
const tone =
|
|
223
239
|
props.priority === "urgent" ? "danger" : props.priority === "high" ? "point" : "neutral";
|
|
224
|
-
return
|
|
240
|
+
return (
|
|
241
|
+
<Badge tone={tone} kind="priority">
|
|
242
|
+
{words.vocabulary.priority[props.priority]}
|
|
243
|
+
</Badge>
|
|
244
|
+
);
|
|
225
245
|
}
|
|
226
246
|
|
|
227
247
|
/** A category as a badge, in its colour: a class the stylesheet paints, never a value typed by hand. */
|
|
@@ -245,7 +265,11 @@ export function GoalStateBadge(props: { readonly state: GoalState }) {
|
|
|
245
265
|
: props.state === "dropped"
|
|
246
266
|
? "neutral"
|
|
247
267
|
: "neutral";
|
|
248
|
-
return
|
|
268
|
+
return (
|
|
269
|
+
<Badge tone={tone} kind="state">
|
|
270
|
+
{words.vocabulary.goalState[props.state]}
|
|
271
|
+
</Badge>
|
|
272
|
+
);
|
|
249
273
|
}
|
|
250
274
|
|
|
251
275
|
/**
|
|
@@ -266,29 +290,45 @@ export function DueBadge(props: {
|
|
|
266
290
|
const days = daysBetween(now, props.dueOn);
|
|
267
291
|
const day = formatDay(props.dueOn, tag);
|
|
268
292
|
if (!open) {
|
|
269
|
-
return
|
|
293
|
+
return (
|
|
294
|
+
<Badge tone="neutral" kind="due">
|
|
295
|
+
{words.board.due.on(day)}
|
|
296
|
+
</Badge>
|
|
297
|
+
);
|
|
270
298
|
}
|
|
271
299
|
if (days < 0) {
|
|
272
300
|
return (
|
|
273
|
-
<Badge tone="danger" title={day}>
|
|
301
|
+
<Badge tone="danger" kind="due" title={day}>
|
|
274
302
|
{words.board.due.overdue(-days)}
|
|
275
303
|
</Badge>
|
|
276
304
|
);
|
|
277
305
|
}
|
|
278
306
|
if (days === 0) {
|
|
279
|
-
return
|
|
307
|
+
return (
|
|
308
|
+
<Badge tone="point" kind="due" className="sc-badge-due-today">
|
|
309
|
+
{words.board.due.today}
|
|
310
|
+
</Badge>
|
|
311
|
+
);
|
|
280
312
|
}
|
|
281
313
|
if (days === 1) {
|
|
282
|
-
return
|
|
314
|
+
return (
|
|
315
|
+
<Badge tone="point" kind="due">
|
|
316
|
+
{words.board.due.tomorrow}
|
|
317
|
+
</Badge>
|
|
318
|
+
);
|
|
283
319
|
}
|
|
284
320
|
if (days <= 7) {
|
|
285
321
|
return (
|
|
286
|
-
<Badge tone="
|
|
322
|
+
<Badge tone="warning" kind="due" title={day}>
|
|
287
323
|
{words.board.due.inDays(days)}
|
|
288
324
|
</Badge>
|
|
289
325
|
);
|
|
290
326
|
}
|
|
291
|
-
return
|
|
327
|
+
return (
|
|
328
|
+
<Badge tone="neutral" kind="due">
|
|
329
|
+
{words.board.due.on(day)}
|
|
330
|
+
</Badge>
|
|
331
|
+
);
|
|
292
332
|
}
|
|
293
333
|
|
|
294
334
|
/** How far a goal is, read from its tasks: the done ones, the ones at work, and the rest. */
|
|
@@ -402,3 +442,16 @@ export function Time(props: {
|
|
|
402
442
|
</time>
|
|
403
443
|
);
|
|
404
444
|
}
|
|
445
|
+
|
|
446
|
+
/** A link that reads as a button: where a form is, for instance. */
|
|
447
|
+
export function LinkButton(props: {
|
|
448
|
+
readonly href: string;
|
|
449
|
+
readonly variant?: "primary" | "secondary";
|
|
450
|
+
readonly children: ReactNode;
|
|
451
|
+
}) {
|
|
452
|
+
return (
|
|
453
|
+
<a className={`sc-button sc-button-${props.variant ?? "secondary"}`} href={props.href}>
|
|
454
|
+
{props.children}
|
|
455
|
+
</a>
|
|
456
|
+
);
|
|
457
|
+
}
|
package/src/console.tsx
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type RestDecisionInput,
|
|
23
23
|
type RestDecisionPatch,
|
|
24
24
|
type RestEvent,
|
|
25
|
+
type RestMyDecisions,
|
|
25
26
|
type RestProject,
|
|
26
27
|
type RestProjectInput,
|
|
27
28
|
type RestProjectPatch,
|
|
@@ -45,6 +46,7 @@ import { DecisionForm } from "./components/decision-form.js";
|
|
|
45
46
|
import { DecisionCard, DecisionList, type DecisionListProps } from "./components/decision-list.js";
|
|
46
47
|
import { EventFeed, type EventFeedProps } from "./components/event-feed.js";
|
|
47
48
|
import { GoalForm, GoalList, type GoalListProps, GoalView, goalMonth } from "./components/goals.js";
|
|
49
|
+
import { MyDecisions } from "./components/my-decisions.js";
|
|
48
50
|
import { PackTokenWays } from "./components/pack-token.js";
|
|
49
51
|
import { PAGE_SIZE } from "./components/paged.js";
|
|
50
52
|
import { ListingForm, PeopleList, type PeopleListProps } from "./components/people.js";
|
|
@@ -76,6 +78,7 @@ import {
|
|
|
76
78
|
Callout,
|
|
77
79
|
CategoryBadge,
|
|
78
80
|
EmptyState,
|
|
81
|
+
LinkButton,
|
|
79
82
|
personName,
|
|
80
83
|
Spinner,
|
|
81
84
|
StateBadge,
|
|
@@ -385,19 +388,6 @@ function NotWithToken() {
|
|
|
385
388
|
return <EmptyState title={words.title} body={words.body} />;
|
|
386
389
|
}
|
|
387
390
|
|
|
388
|
-
/** A link that reads as a button: where a form is, for instance. */
|
|
389
|
-
function LinkButton(props: {
|
|
390
|
-
readonly href: string;
|
|
391
|
-
readonly variant?: "primary" | "secondary";
|
|
392
|
-
readonly children: React.ReactNode;
|
|
393
|
-
}) {
|
|
394
|
-
return (
|
|
395
|
-
<a className={`sc-button sc-button-${props.variant ?? "secondary"}`} href={props.href}>
|
|
396
|
-
{props.children}
|
|
397
|
-
</a>
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
391
|
function ProjectsPage(props: {
|
|
402
392
|
readonly data: ConsoleData;
|
|
403
393
|
readonly components: ConsoleComponents;
|
|
@@ -913,6 +903,19 @@ function ProjectPage(props: {
|
|
|
913
903
|
empty={<p className="sc-muted">{calendar.nothingOverdue}</p>}
|
|
914
904
|
/>
|
|
915
905
|
</section>
|
|
906
|
+
{open.length > 0 && (
|
|
907
|
+
<section className="sc-task-section" aria-label={calendar.workload.title}>
|
|
908
|
+
<h2 className="sc-section-title">{calendar.workload.title}</h2>
|
|
909
|
+
<Workload
|
|
910
|
+
tasks={shown}
|
|
911
|
+
today={now}
|
|
912
|
+
weekStart={weekStartOf(data.me?.workspace)}
|
|
913
|
+
personHref={(person) =>
|
|
914
|
+
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
915
|
+
}
|
|
916
|
+
/>
|
|
917
|
+
</section>
|
|
918
|
+
)}
|
|
916
919
|
<section className="sc-task-section" aria-label={calendar.unscheduled}>
|
|
917
920
|
<h2 className="sc-section-title">
|
|
918
921
|
{calendar.unscheduled} <span className="sc-column-count">{unscheduled.length}</span>
|
|
@@ -926,19 +929,6 @@ function ProjectPage(props: {
|
|
|
926
929
|
/>
|
|
927
930
|
</section>
|
|
928
931
|
</div>
|
|
929
|
-
{open.length > 0 && (
|
|
930
|
-
<section className="sc-task-section" aria-label={calendar.workload.title}>
|
|
931
|
-
<h2 className="sc-section-title">{calendar.workload.title}</h2>
|
|
932
|
-
<Workload
|
|
933
|
-
tasks={shown}
|
|
934
|
-
today={now}
|
|
935
|
-
weekStart={weekStartOf(data.me?.workspace)}
|
|
936
|
-
personHref={(person) =>
|
|
937
|
-
boardHref(key, person === undefined ? {} : { person: person.id })
|
|
938
|
-
}
|
|
939
|
-
/>
|
|
940
|
-
</section>
|
|
941
|
-
)}
|
|
942
932
|
</>
|
|
943
933
|
);
|
|
944
934
|
}
|
|
@@ -1169,7 +1159,9 @@ function ProjectPage(props: {
|
|
|
1169
1159
|
{error !== undefined && <Callout tone="danger">{error}</Callout>}
|
|
1170
1160
|
<section className="sc-panel sc-panel-form" aria-label={words.decisions.raiseAria}>
|
|
1171
1161
|
<DecisionForm
|
|
1172
|
-
|
|
1162
|
+
findTasks={data.actions.findTasks}
|
|
1163
|
+
deciders={data.members.map((member) => member.person)}
|
|
1164
|
+
me={data.me?.person ?? undefined}
|
|
1173
1165
|
busy={busy}
|
|
1174
1166
|
onSubmit={(input: RestDecisionInput) =>
|
|
1175
1167
|
void act(async () => {
|
|
@@ -2012,6 +2004,108 @@ function PlatePage(props: {
|
|
|
2012
2004
|
}
|
|
2013
2005
|
|
|
2014
2006
|
/** What is on the person's own plate, across their projects. */
|
|
2007
|
+
/** How many of a person's decisions are read at a time. */
|
|
2008
|
+
const MY_DECISIONS_PAGE = 50;
|
|
2009
|
+
|
|
2010
|
+
/** One kind of a person's decisions, read a page at a time, as the page shows it. */
|
|
2011
|
+
interface DecisionsRead {
|
|
2012
|
+
readonly items: RestMyDecisions["items"] | undefined;
|
|
2013
|
+
readonly more: boolean;
|
|
2014
|
+
readonly busy: boolean;
|
|
2015
|
+
readonly problem: string | undefined;
|
|
2016
|
+
readonly readMore: () => void;
|
|
2017
|
+
/** Reads the first page again, after an answer changed what waits. */
|
|
2018
|
+
readonly reload: () => void;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/** The decisions addressed to the person, or, with `raised`, those they raised for someone else (ADR-0042). */
|
|
2022
|
+
function useMyDecisions(
|
|
2023
|
+
myDecisions: ConsoleData["actions"]["myDecisions"],
|
|
2024
|
+
raised: boolean,
|
|
2025
|
+
couldNotLoad: string,
|
|
2026
|
+
): DecisionsRead {
|
|
2027
|
+
const [items, setItems] = useState<RestMyDecisions["items"] | undefined>(undefined);
|
|
2028
|
+
const [more, setMore] = useState(false);
|
|
2029
|
+
const [busy, setBusy] = useState(false);
|
|
2030
|
+
const [problem, setProblem] = useState<string | undefined>(undefined);
|
|
2031
|
+
const read = useCallback(
|
|
2032
|
+
async (offset: number, signal?: AbortSignal) => {
|
|
2033
|
+
setBusy(true);
|
|
2034
|
+
try {
|
|
2035
|
+
const page = await myDecisions({ raised, limit: MY_DECISIONS_PAGE, offset }, signal);
|
|
2036
|
+
if (signal?.aborted === true) {
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
setItems((before) => (offset === 0 ? page.items : [...(before ?? []), ...page.items]));
|
|
2040
|
+
setMore(page.more);
|
|
2041
|
+
} catch (failure: unknown) {
|
|
2042
|
+
if (signal?.aborted !== true) {
|
|
2043
|
+
setProblem(errorWords(failure, couldNotLoad));
|
|
2044
|
+
}
|
|
2045
|
+
} finally {
|
|
2046
|
+
if (signal?.aborted !== true) {
|
|
2047
|
+
setBusy(false);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
},
|
|
2051
|
+
[myDecisions, raised, couldNotLoad],
|
|
2052
|
+
);
|
|
2053
|
+
useEffect(() => {
|
|
2054
|
+
const controller = new AbortController();
|
|
2055
|
+
setItems(undefined);
|
|
2056
|
+
setProblem(undefined);
|
|
2057
|
+
void read(0, controller.signal);
|
|
2058
|
+
return () => controller.abort();
|
|
2059
|
+
}, [read]);
|
|
2060
|
+
const readMore = useCallback(() => void read(items?.length ?? 0), [read, items]);
|
|
2061
|
+
const reload = useCallback(() => void read(0), [read]);
|
|
2062
|
+
return { items, more, busy, problem, readMore, reload };
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
/**
|
|
2066
|
+
* A person's decisions (ADR-0042), laid out as a project's decisions page is: those waiting for
|
|
2067
|
+
* their answer, answered here, those they asked of others, and the record of both.
|
|
2068
|
+
*/
|
|
2069
|
+
function MyDecisionsPage(props: { readonly data: ConsoleData }) {
|
|
2070
|
+
const words = useWords();
|
|
2071
|
+
const { myDecisions, answerDecisionIn } = props.data.actions;
|
|
2072
|
+
const addressed = useMyDecisions(myDecisions, false, words.myDecisions.couldNotLoad);
|
|
2073
|
+
const raised = useMyDecisions(myDecisions, true, words.myDecisions.couldNotLoad);
|
|
2074
|
+
const { busy, error, act } = useAction();
|
|
2075
|
+
const problem = addressed.problem ?? raised.problem ?? error;
|
|
2076
|
+
const onAnswer = (projectKey: string, decision: RestDecision, input: RestAnswerInput) =>
|
|
2077
|
+
void act(async () => {
|
|
2078
|
+
await answerDecisionIn(projectKey, decision.id, input);
|
|
2079
|
+
addressed.reload();
|
|
2080
|
+
raised.reload();
|
|
2081
|
+
});
|
|
2082
|
+
return (
|
|
2083
|
+
<>
|
|
2084
|
+
<div className="sc-page-head">
|
|
2085
|
+
<h1 className="sc-page-title">{words.myDecisions.title}</h1>
|
|
2086
|
+
</div>
|
|
2087
|
+
{problem !== undefined && <Callout tone="danger">{problem}</Callout>}
|
|
2088
|
+
{addressed.items === undefined || raised.items === undefined ? (
|
|
2089
|
+
problem === undefined && (
|
|
2090
|
+
<div className="sc-loading">
|
|
2091
|
+
<Spinner label={words.myDecisions.loading} />
|
|
2092
|
+
</div>
|
|
2093
|
+
)
|
|
2094
|
+
) : addressed.items.length === 0 && raised.items.length === 0 ? (
|
|
2095
|
+
<EmptyState title={words.myDecisions.empty.title} body={words.myDecisions.empty.body} />
|
|
2096
|
+
) : (
|
|
2097
|
+
<MyDecisions
|
|
2098
|
+
addressed={{ items: addressed.items, more: addressed.more, onMore: addressed.readMore }}
|
|
2099
|
+
raised={{ items: raised.items, more: raised.more, onMore: raised.readMore }}
|
|
2100
|
+
decisionHref={decisionHref}
|
|
2101
|
+
onAnswer={onAnswer}
|
|
2102
|
+
busy={busy || addressed.busy || raised.busy}
|
|
2103
|
+
/>
|
|
2104
|
+
)}
|
|
2105
|
+
</>
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2015
2109
|
function WorkPage(props: {
|
|
2016
2110
|
readonly data: ConsoleData;
|
|
2017
2111
|
readonly components: ConsoleComponents;
|
|
@@ -2078,6 +2172,9 @@ function WorkspacePage(props: {
|
|
|
2078
2172
|
if (route.name === "work") {
|
|
2079
2173
|
return <WorkPage data={data} components={components} week={route.week} />;
|
|
2080
2174
|
}
|
|
2175
|
+
if (route.name === "my-decisions") {
|
|
2176
|
+
return <MyDecisionsPage data={data} />;
|
|
2177
|
+
}
|
|
2081
2178
|
if (route.name === "person-work") {
|
|
2082
2179
|
return <PersonWorkPage id={route.id} data={data} components={components} week={route.week} />;
|
|
2083
2180
|
}
|
|
@@ -2321,6 +2418,11 @@ export function createConsole(config: ConsoleConfig = {}): ConsoleApp {
|
|
|
2321
2418
|
current: route.name === "projects" || route.name === "new-project",
|
|
2322
2419
|
},
|
|
2323
2420
|
{ href: PATHS.work, label: words.nav.work, current: route.name === "work" },
|
|
2421
|
+
{
|
|
2422
|
+
href: PATHS.decisions,
|
|
2423
|
+
label: words.nav.myDecisions,
|
|
2424
|
+
current: route.name === "my-decisions",
|
|
2425
|
+
},
|
|
2324
2426
|
{
|
|
2325
2427
|
href: PATHS.people,
|
|
2326
2428
|
label: words.nav.people,
|
package/src/data.ts
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
type RestMemberInput,
|
|
33
33
|
type RestMemberPatch,
|
|
34
34
|
type RestMePatch,
|
|
35
|
+
type RestMyDecisions,
|
|
35
36
|
type RestOrganization,
|
|
36
37
|
type RestPackConnect,
|
|
37
38
|
type RestPackConnectStarted,
|
|
@@ -122,6 +123,8 @@ export interface ConsoleActions {
|
|
|
122
123
|
): Promise<RestGoals>;
|
|
123
124
|
raiseDecision(input: RestDecisionInput): Promise<RestDecision>;
|
|
124
125
|
answerDecision(id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
126
|
+
/** Answers a decision of any project the person may see, by its project's key: for the person's own page of decisions (ADR-0042). */
|
|
127
|
+
answerDecisionIn(projectKey: string, id: string, input: RestAnswerInput): Promise<RestDecision>;
|
|
125
128
|
/** Grows a decision's record: its context, or what followed. */
|
|
126
129
|
updateDecision(id: string, patch: RestDecisionPatch): Promise<RestDecision>;
|
|
127
130
|
/** Sets a goal on the roadmap of the project the page is on; for an owner. */
|
|
@@ -129,6 +132,17 @@ export interface ConsoleActions {
|
|
|
129
132
|
updateGoal(id: string, patch: RestGoalPatch): Promise<RestGoal>;
|
|
130
133
|
/** What is on the person's plate across their projects, read when the page asks. */
|
|
131
134
|
work(signal?: AbortSignal): Promise<RestWork>;
|
|
135
|
+
/** The decisions addressed to the person (ADR-0042) across their projects, a page at a time, read when the page asks. */
|
|
136
|
+
myDecisions(
|
|
137
|
+
filter: {
|
|
138
|
+
readonly open?: boolean;
|
|
139
|
+
/** Those the person raised for someone else, in place of those addressed to them. */
|
|
140
|
+
readonly raised?: boolean;
|
|
141
|
+
readonly limit: number;
|
|
142
|
+
readonly offset: number;
|
|
143
|
+
},
|
|
144
|
+
signal?: AbortSignal,
|
|
145
|
+
): Promise<RestMyDecisions>;
|
|
132
146
|
/** The categories tasks are sorted into: kept by an administrator. */
|
|
133
147
|
createCategory(input: RestCategoryInput): Promise<RestCategory>;
|
|
134
148
|
updateCategory(key: string, patch: RestCategoryPatch): Promise<RestCategory>;
|
|
@@ -656,6 +670,8 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
656
670
|
await refreshProject();
|
|
657
671
|
return decision;
|
|
658
672
|
},
|
|
673
|
+
answerDecisionIn: (projectKey, id, input) =>
|
|
674
|
+
client.project(projectKey).answerDecision(id, input),
|
|
659
675
|
async answerDecision(id, input) {
|
|
660
676
|
const decision = await scope().answerDecision(id, input);
|
|
661
677
|
await refreshProject();
|
|
@@ -679,6 +695,7 @@ export function useConsoleData(client: ConsoleClient, projectKey: string | undef
|
|
|
679
695
|
return goal;
|
|
680
696
|
},
|
|
681
697
|
work: (signal) => client.work(signal),
|
|
698
|
+
myDecisions: (filter, signal) => client.myDecisions(filter, signal),
|
|
682
699
|
async createCategory(input) {
|
|
683
700
|
const category = await client.createCategory(input);
|
|
684
701
|
setCategories((await client.categories()).items);
|
package/src/i18n/en.ts
CHANGED
|
@@ -131,6 +131,7 @@ export const en = {
|
|
|
131
131
|
nav: {
|
|
132
132
|
projects: "Projects",
|
|
133
133
|
work: "My work",
|
|
134
|
+
myDecisions: "My decisions",
|
|
134
135
|
people: "People",
|
|
135
136
|
agents: "Agents",
|
|
136
137
|
board: "Board",
|
|
@@ -279,8 +280,11 @@ export const en = {
|
|
|
279
280
|
menu: "Task menu",
|
|
280
281
|
decisions: (n: number) => count(n, "decision", "decisions"),
|
|
281
282
|
decisionsTitle: "Decisions waiting for a person",
|
|
283
|
+
unfold: (column: string) => `Open ${column}`,
|
|
284
|
+
fold: (column: string) => `Fold ${column}`,
|
|
282
285
|
filters: {
|
|
283
286
|
aria: "Narrow the board",
|
|
287
|
+
words: "Title, number or person",
|
|
284
288
|
everyone: "Everyone",
|
|
285
289
|
mine: "Mine",
|
|
286
290
|
anyCategory: "Any category",
|
|
@@ -380,6 +384,8 @@ export const en = {
|
|
|
380
384
|
record: "The record",
|
|
381
385
|
openRecord: "Open",
|
|
382
386
|
waitingForPerson: "Waiting for a person.",
|
|
387
|
+
waitingFor: (name: string) => `Waiting for ${name}.`,
|
|
388
|
+
peopleLabel: (asker: string, decider: string) => `asked by ${asker}, for ${decider}`,
|
|
383
389
|
options: "Options",
|
|
384
390
|
notePlaceholder: "A word about it, if any",
|
|
385
391
|
own: "Something else",
|
|
@@ -402,6 +408,9 @@ export const en = {
|
|
|
402
408
|
contextPlaceholder: "What a person needs to know to answer, in Markdown.",
|
|
403
409
|
about: "About",
|
|
404
410
|
noTask: "No task in particular",
|
|
411
|
+
decider: "Who decides",
|
|
412
|
+
deciderPlaceholder: "You, unless you name someone",
|
|
413
|
+
me: "(you)",
|
|
405
414
|
options: "Options",
|
|
406
415
|
option: (n: number) => `Option ${n}`,
|
|
407
416
|
addOption: "Add an option",
|
|
@@ -409,6 +418,23 @@ export const en = {
|
|
|
409
418
|
},
|
|
410
419
|
},
|
|
411
420
|
|
|
421
|
+
myDecisions: {
|
|
422
|
+
title: "My decisions",
|
|
423
|
+
loading: "Loading your decisions",
|
|
424
|
+
couldNotLoad: "Your decisions could not be loaded.",
|
|
425
|
+
empty: {
|
|
426
|
+
title: "No decision is yours",
|
|
427
|
+
body: "A decision for you, by you or your agent or by someone who named you, and one you asked of someone else, show up here.",
|
|
428
|
+
},
|
|
429
|
+
waiting: "Waiting for you",
|
|
430
|
+
noneWaiting: "Nothing waits for your answer.",
|
|
431
|
+
raised: "Asked of others",
|
|
432
|
+
noneRaised: "You have asked nothing of others.",
|
|
433
|
+
answered: "Answered",
|
|
434
|
+
noneAnswered: "Nothing answered yet.",
|
|
435
|
+
more: "Show more",
|
|
436
|
+
},
|
|
437
|
+
|
|
412
438
|
calendar: {
|
|
413
439
|
title: "Calendar",
|
|
414
440
|
grid: "The month",
|
|
@@ -421,6 +447,11 @@ export const en = {
|
|
|
421
447
|
nothingOverdue: "Nothing is overdue.",
|
|
422
448
|
unscheduled: "No day set",
|
|
423
449
|
allScheduled: "Every open task has a day.",
|
|
450
|
+
more: (n: number) => `${n} more`,
|
|
451
|
+
fewer: "Fewer",
|
|
452
|
+
onDay: (day: string) => `Tasks on ${day}`,
|
|
453
|
+
nothingOnDay: "Nothing is on this day.",
|
|
454
|
+
close: "Close",
|
|
424
455
|
week: {
|
|
425
456
|
title: "The week",
|
|
426
457
|
previous: "The week before",
|
|
@@ -512,8 +543,8 @@ export const en = {
|
|
|
512
543
|
thisWeek: "Due this week",
|
|
513
544
|
later: "Later",
|
|
514
545
|
noDay: "No day set",
|
|
515
|
-
decisions: "Decisions waiting for
|
|
516
|
-
noDecisions: "No decision waits.",
|
|
546
|
+
decisions: "Decisions waiting for you",
|
|
547
|
+
noDecisions: "No decision waits for your answer.",
|
|
517
548
|
inProject: (project: string) => `in ${project}`,
|
|
518
549
|
},
|
|
519
550
|
|
|
@@ -677,6 +708,8 @@ export const en = {
|
|
|
677
708
|
runFinished: (task: string) => `finished ${task}`,
|
|
678
709
|
runFailed: (task: string) => `failed on ${task}`,
|
|
679
710
|
runAbandoned: (task: string) => `gave up on ${task}`,
|
|
711
|
+
runUnheard: (task: string, agent: string) =>
|
|
712
|
+
`ended the run of ${agent} on ${task}, not heard from for too long`,
|
|
680
713
|
documentWritten: (page: string, version: number | undefined) =>
|
|
681
714
|
`wrote ${page}${version === undefined ? "" : `, version ${version}`}`,
|
|
682
715
|
documentArchived: (page: string) => `archived ${page}`,
|
|
@@ -801,6 +834,7 @@ export const en = {
|
|
|
801
834
|
version: (n: number) => `v${n}`,
|
|
802
835
|
versionWord: "version",
|
|
803
836
|
writtenBy: "written by",
|
|
837
|
+
author: "Written by",
|
|
804
838
|
versionOf: (n: number, of: number) => `Version ${n} of ${of}`,
|
|
805
839
|
showLatest: "Show the latest",
|
|
806
840
|
archivedNotice: "This page is archived: out of the folders and the search, and not written to.",
|
|
@@ -813,6 +847,8 @@ export const en = {
|
|
|
813
847
|
decisionPrefix: "Decision: ",
|
|
814
848
|
files: "Files",
|
|
815
849
|
fileToAttach: "A file to attach",
|
|
850
|
+
chooseFile: "Choose a file",
|
|
851
|
+
noFileChosen: "No file chosen",
|
|
816
852
|
labelPlaceholder: "What to call it, if not its name",
|
|
817
853
|
attach: "Attach",
|
|
818
854
|
by: "by",
|
package/src/i18n/ko.ts
CHANGED
|
@@ -93,6 +93,7 @@ export const ko: Messages = {
|
|
|
93
93
|
nav: {
|
|
94
94
|
projects: "프로젝트",
|
|
95
95
|
work: "내 작업",
|
|
96
|
+
myDecisions: "내 결정",
|
|
96
97
|
people: "사람들",
|
|
97
98
|
agents: "에이전트",
|
|
98
99
|
board: "보드",
|
|
@@ -239,8 +240,11 @@ export const ko: Messages = {
|
|
|
239
240
|
menu: "작업 메뉴",
|
|
240
241
|
decisions: (n) => `결정 ${n}건`,
|
|
241
242
|
decisionsTitle: "사람을 기다리는 결정",
|
|
243
|
+
unfold: (column) => `${column} 펼치기`,
|
|
244
|
+
fold: (column) => `${column} 접기`,
|
|
242
245
|
filters: {
|
|
243
246
|
aria: "보드 좁혀 보기",
|
|
247
|
+
words: "제목, 번호, 사람",
|
|
244
248
|
everyone: "모두",
|
|
245
249
|
mine: "내 작업",
|
|
246
250
|
anyCategory: "모든 카테고리",
|
|
@@ -340,6 +344,8 @@ export const ko: Messages = {
|
|
|
340
344
|
record: "기록",
|
|
341
345
|
openRecord: "열기",
|
|
342
346
|
waitingForPerson: "사람을 기다리는 중입니다.",
|
|
347
|
+
waitingFor: (name) => `${name}의 답을 기다리는 중입니다.`,
|
|
348
|
+
peopleLabel: (asker, decider) => `올린 사람 ${asker}, 답할 사람 ${decider}`,
|
|
343
349
|
options: "선택지",
|
|
344
350
|
notePlaceholder: "덧붙일 말이 있다면",
|
|
345
351
|
own: "직접 입력",
|
|
@@ -362,6 +368,9 @@ export const ko: Messages = {
|
|
|
362
368
|
contextPlaceholder: "답하려면 알아야 할 것, Markdown으로.",
|
|
363
369
|
about: "관련 작업",
|
|
364
370
|
noTask: "특정 작업 없음",
|
|
371
|
+
decider: "결정할 사람",
|
|
372
|
+
deciderPlaceholder: "지정하지 않으면 본인",
|
|
373
|
+
me: "(나)",
|
|
365
374
|
options: "선택지",
|
|
366
375
|
option: (n) => `선택지 ${n}`,
|
|
367
376
|
addOption: "선택지 추가",
|
|
@@ -369,6 +378,23 @@ export const ko: Messages = {
|
|
|
369
378
|
},
|
|
370
379
|
},
|
|
371
380
|
|
|
381
|
+
myDecisions: {
|
|
382
|
+
title: "내 결정",
|
|
383
|
+
loading: "내 결정을 불러오는 중",
|
|
384
|
+
couldNotLoad: "내 결정을 불러올 수 없습니다.",
|
|
385
|
+
empty: {
|
|
386
|
+
title: "내 결정이 없습니다",
|
|
387
|
+
body: "내게 온 결정과 내가 다른 사람에게 올린 결정이 여기에 모입니다. 내가 올린 결정은 내 에이전트가 올린 것을 포함합니다.",
|
|
388
|
+
},
|
|
389
|
+
waiting: "내 답을 기다리는 중",
|
|
390
|
+
noneWaiting: "내 답을 기다리는 결정이 없습니다.",
|
|
391
|
+
raised: "내가 다른 사람에게 올린 결정",
|
|
392
|
+
noneRaised: "다른 사람에게 올린 결정이 없습니다.",
|
|
393
|
+
answered: "답변됨",
|
|
394
|
+
noneAnswered: "아직 답한 결정이 없습니다.",
|
|
395
|
+
more: "더 보기",
|
|
396
|
+
},
|
|
397
|
+
|
|
372
398
|
calendar: {
|
|
373
399
|
title: "캘린더",
|
|
374
400
|
grid: "이 달",
|
|
@@ -381,6 +407,11 @@ export const ko: Messages = {
|
|
|
381
407
|
nothingOverdue: "기한이 지난 작업이 없습니다.",
|
|
382
408
|
unscheduled: "날짜 없음",
|
|
383
409
|
allScheduled: "열린 작업마다 날짜가 있습니다.",
|
|
410
|
+
more: (n) => `${n}개 더`,
|
|
411
|
+
fewer: "접기",
|
|
412
|
+
onDay: (day) => `${day}의 작업`,
|
|
413
|
+
nothingOnDay: "이 날에 잡힌 작업이 없습니다.",
|
|
414
|
+
close: "닫기",
|
|
384
415
|
week: {
|
|
385
416
|
title: "이 주",
|
|
386
417
|
previous: "이전 주",
|
|
@@ -389,7 +420,7 @@ export const ko: Messages = {
|
|
|
389
420
|
nothing: "이 주에 잡힌 작업이 없습니다.",
|
|
390
421
|
},
|
|
391
422
|
workload: {
|
|
392
|
-
title: "
|
|
423
|
+
title: "담당자별 작업 현황",
|
|
393
424
|
person: "사람",
|
|
394
425
|
open: "열림",
|
|
395
426
|
inProgress: "진행 중",
|
|
@@ -471,8 +502,8 @@ export const ko: Messages = {
|
|
|
471
502
|
thisWeek: "이번 주 마감",
|
|
472
503
|
later: "그 뒤",
|
|
473
504
|
noDay: "날짜 없음",
|
|
474
|
-
decisions: "
|
|
475
|
-
noDecisions: "기다리는 결정이 없습니다.",
|
|
505
|
+
decisions: "내 답을 기다리는 결정",
|
|
506
|
+
noDecisions: "내 답을 기다리는 결정이 없습니다.",
|
|
476
507
|
inProject: (project) => `${project}에서`,
|
|
477
508
|
},
|
|
478
509
|
|
|
@@ -632,6 +663,8 @@ export const ko: Messages = {
|
|
|
632
663
|
runFinished: (task) => `${task}을(를) 끝냈습니다`,
|
|
633
664
|
runFailed: (task) => `${task}에 실패했습니다`,
|
|
634
665
|
runAbandoned: (task) => `${task}을(를) 포기했습니다`,
|
|
666
|
+
runUnheard: (task, agent) =>
|
|
667
|
+
`${task}의 ${agent} 런을 끝냈습니다. 너무 오래 소식이 없었습니다`,
|
|
635
668
|
documentWritten: (page, version) =>
|
|
636
669
|
`${page}을(를) 썼습니다${version === undefined ? "" : `, 버전 ${version}`}`,
|
|
637
670
|
documentArchived: (page) => `${page}을(를) 보관했습니다`,
|
|
@@ -757,6 +790,7 @@ export const ko: Messages = {
|
|
|
757
790
|
version: (n) => `v${n}`,
|
|
758
791
|
versionWord: "버전",
|
|
759
792
|
writtenBy: "작성:",
|
|
793
|
+
author: "작성",
|
|
760
794
|
versionOf: (n, of) => `버전 ${n} / ${of}`,
|
|
761
795
|
showLatest: "최신 버전 보기",
|
|
762
796
|
archivedNotice: "이 페이지는 보관됐습니다. 폴더와 검색에서 빠지고, 쓸 수 없습니다.",
|
|
@@ -769,6 +803,8 @@ export const ko: Messages = {
|
|
|
769
803
|
decisionPrefix: "결정: ",
|
|
770
804
|
files: "파일",
|
|
771
805
|
fileToAttach: "첨부할 파일",
|
|
806
|
+
chooseFile: "파일 선택",
|
|
807
|
+
noFileChosen: "선택된 파일 없음",
|
|
772
808
|
labelPlaceholder: "파일 이름 대신 부를 이름",
|
|
773
809
|
attach: "첨부",
|
|
774
810
|
by: "첨부:",
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export {
|
|
|
11
11
|
type BoardProps,
|
|
12
12
|
byDue,
|
|
13
13
|
columnStateOf,
|
|
14
|
+
FOLDED_STATES,
|
|
14
15
|
filterTasks,
|
|
15
16
|
TaskCard,
|
|
16
17
|
} from "./components/board.js";
|
|
@@ -82,6 +83,7 @@ export {
|
|
|
82
83
|
goalMonth,
|
|
83
84
|
} from "./components/goals.js";
|
|
84
85
|
export { Markdown, type MarkdownProps } from "./components/markdown.js";
|
|
86
|
+
export { MyDecisions, type MyDecisionsProps } from "./components/my-decisions.js";
|
|
85
87
|
export {
|
|
86
88
|
PackConnect,
|
|
87
89
|
type PackConnectProps,
|
|
@@ -207,6 +209,7 @@ export {
|
|
|
207
209
|
formatBytes,
|
|
208
210
|
formatInstant,
|
|
209
211
|
GoalStateBadge,
|
|
212
|
+
LinkButton,
|
|
210
213
|
PersonChip,
|
|
211
214
|
PRIORITY_LABELS,
|
|
212
215
|
PriorityBadge,
|