Public: anyone can read this; the crew can change it.
The clock says how many people need a nudge and links to People #236
Check · Review · Land · Live — Its check is red.
mason/deal-clock:mason/233-the-clock-says-how-many-people-need-a-nu → main 1 commit fork
4 files
changed +35
−0
docs/design/areas/clock.md +2
| @@ -7,3 +7,5 @@ New classes: | ||
| 7 | 7 | - `.progress` — the paragraph that holds both at the top of a deal's page. |
| 8 | 8 | |
| 9 | 9 | - `.closing-clash` — one amber line (`--color-soon`) under a closing on This week, the print sheet and the deal page: "Also closing at 2:30 pm: 2210 Juniper Court", the address linked to its deal. |
| 10 | ||
| 11 | - `.clock-nudge` — one amber line (`--color-soon`) under the clock's heading, linking to People: "2 people owe a date soon and have heard nothing from you in a week". Absent when nobody needs a nudge. |
src/lastsent.test.ts +18
| @@ -48,3 +48,21 @@ test("the sample data on a fresh boot flags at least one person on /people, esca | ||
| 48 | 48 | expect(html).toContain('class="person-nudge"'); |
| 49 | 49 | expect(html).toContain("Owes "); |
| 50 | 50 | }); |
| 51 | ||
| 52 | test("the clock counts the people to nudge, links to People, and says nothing when there are none", async () => { | |
| 53 | const { refresh } = await import("./sample.ts"); | |
| 54 | const { clock } = await import("./pages/clock.ts"); | |
| 55 | const fresh = openDb(mkdtempSync(join(tmpdir(), "app-clocknudge-"))); | |
| 56 | refresh(fresh); | |
| 57 | const l = listDeals(fresh, alice); | |
| 58 | if (!l.ok) throw new Error(l.message); | |
| 59 | const on = today(DEFAULT_ZONE); | |
| 60 | const n = people(l.value, on).filter((p) => p.nudge !== null).length; | |
| 61 | expect(n).toBeGreaterThan(0); | |
| 62 | const html = clock({ who: alice, deals: l.value, on }); | |
| 63 | expect(html).toContain(n === 1 ? "1 person owes a date soon and has heard nothing from you in a week" : `${n} people owe a date soon and have heard nothing from you in a week`); | |
| 64 | expect(html).toContain('<a href="/people">'); | |
| 65 | // Far in the future nothing is due within three days of today. | |
| 66 | expect(clock({ who: alice, deals: l.value, on: "2099-01-01" })).not.toContain("clock-nudge"); | |
| 67 | expect(clock({ who: alice, deals: [], on })).not.toContain("clock-nudge"); | |
| 68 | }); |
src/pages/clock.ts +8
| @@ -25,6 +25,7 @@ import { memoizer, print } from "../memo.ts"; | ||
| 25 | 25 | import { bookLine, tally } from "../book.ts"; |
| 26 | 26 | import { dollars } from "../money.ts"; |
| 27 | 27 | import { progressLine } from "./progress.ts"; |
| 28 | import { people } from "../people.ts"; | |
| 28 | 29 | |
| 29 | 30 | /** The days the clock shows: a week behind today (further, if something open is older), to a few days past the last closing. */ |
| 30 | 31 | export function span(deals: readonly DealView[], on: string): { start: string; days: number } { |
| @@ -230,6 +231,12 @@ function render(input: { | ||
| 230 | 231 | <form method="post" action="/samples/restore"><button class="button">Yes, restore them</button></form> |
| 231 | 232 | </details> |
| 232 | 233 | </section>`; |
| 234 | // The same rule as the People page, counted for the agent's today. | |
| 235 | const owing = who.role === "anon" ? 0 : people(input.deals, on).filter((p) => p.nudge !== null).length; | |
| 236 | const nudgeNote = | |
| 237 | owing === 0 | |
| 238 | ? "" | |
| 239 | : `<p class="clock-nudge"><a href="/people">${owing} ${owing === 1 ? "person owes" : "people owe"} a date soon and ${owing === 1 ? "has" : "have"} heard nothing from you in a week</a></p>`; | |
| 233 | 240 | const body = `<section class="intro"> |
| 234 | 241 | <div> |
| 235 | 242 | <h1>The clock</h1> |
| @@ -238,6 +245,7 @@ function render(input: { | ||
| 238 | 245 | </div> |
| 239 | 246 | ${who.role === "anon" ? "" : `<span class="actions"><a class="button button-quiet" href="/deals/ics">Add all to calendar</a> <a class="button button-quiet" href="/deals/csv" download>Download spreadsheet</a> <a class="button" href="/deals/new">New deal</a></span>`} |
| 240 | 247 | </section> |
| 248 | ${nudgeNote} | |
| 241 | 249 | <form class="find" method="get" action="/" role="search"> |
| 242 | 250 | <label for="find-q">Find a deal</label> |
| 243 | 251 | <input id="find-q" class="input" type="search" name="q" value="${escape(q)}" maxlength="100" placeholder="Address, client or party" autocomplete="off"> |
static/app.css +7
| @@ -936,6 +936,13 @@ a.marker { | ||
| 936 | 936 | margin: var() 0; |
| 937 | 937 | color: var(); |
| 938 | 938 | } |
| 939 | .clock-nudge { | |
| 940 | margin: var() 0; | |
| 941 | color: var(); | |
| 942 | } | |
| 943 | .clock-nudge a { | |
| 944 | color: inherit; | |
| 945 | } | |
| 939 | 946 | .closing-clash a { |
| 940 | 947 | color: inherit; |
| 941 | 948 | } |