@arcforgelabs/flow 2026.10.42 → 2026.10.44
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 +1 -0
- package/npm-shrinkwrap.json +4 -3
- package/package.json +5 -3
- package/server/auth/people.js +40 -2
- package/server/auth/routes.js +11 -0
- package/server/auth/session.js +22 -3
- package/server/core/custom-fields.js +4 -3
- package/server/core/invoice-rules.js +8 -3
- package/server/core/job-match.js +6 -2
- package/server/core/sending.js +4 -3
- package/server/integrations/sentry/index.js +9 -1
- package/server/release-channel.js +5 -2
- package/server/ui/app-page.js +1 -1
- package/server/ui/app.js +46 -162
- package/server/ui/automations.js +9 -3
- package/server/ui/columns.js +268 -0
- package/server/ui/custom-fields.js +17 -16
- package/server/ui/flow.css +268 -55
- package/server/ui/invoice-notes.js +7 -5
- package/server/ui/invoice-rules.js +3 -3
- package/server/ui/invoicing-settings.js +92 -35
- package/server/ui/invoicing.js +206 -119
- package/server/ui/job-board.js +20 -8
- package/server/ui/job-change.js +36 -0
- package/server/ui/job-view.js +255 -60
- package/server/ui/jobs.js +197 -145
- package/server/ui/kit.js +278 -0
- package/server/ui/list-views.js +73 -0
- package/server/ui/monitoring.js +1 -0
- package/server/ui/person.js +29 -0
- package/server/ui/price-list.js +2 -3
- package/server/ui/price-reconcile.js +7 -8
- package/server/ui/reconcile.js +114 -61
- package/server/ui/routes.js +1 -1
- package/server/ui/saved-views.js +141 -0
- package/server/ui/search.js +8 -0
- package/server/ui/sidecar.js +72 -0
- package/server/ui/tip.js +99 -0
- package/server/ui/view-bar.js +176 -70
- package/server/ui/xero-import.js +7 -10
package/server/ui/flow.css
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
:root {
|
|
9
9
|
--fx-line: color-mix(in srgb, var(--t-text) 12%, transparent);
|
|
10
|
+
/* The side space of every page. Pages use the whole width with this slim
|
|
11
|
+
edge, as ClickUp and HubSpot do; the title, toolbar and content start at
|
|
12
|
+
one place on every page. */
|
|
13
|
+
--fx-gutter: clamp(16px, 2vw, 24px);
|
|
10
14
|
--fx-line-soft: color-mix(in srgb, var(--t-text) 7%, transparent);
|
|
11
15
|
--fx-hover: color-mix(in srgb, var(--t-text) 5%, transparent);
|
|
12
16
|
--fx-focus: 0 0 0 3px color-mix(in srgb, var(--t-ring) 40%, transparent);
|
|
@@ -90,6 +94,9 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
90
94
|
}
|
|
91
95
|
.fx-input:hover, .fx-select:hover { border-color: var(--t-text); }
|
|
92
96
|
.fx-input:focus, .fx-select:focus { outline: none; border-color: var(--t-text); box-shadow: var(--fx-focus); }
|
|
97
|
+
.fx-input[aria-invalid="true"], .fx-select[aria-invalid="true"] {
|
|
98
|
+
border-color: var(--t-danger); box-shadow: 0 0 0 3px color-mix(in srgb, var(--t-danger) 28%, transparent);
|
|
99
|
+
}
|
|
93
100
|
.fx-select {
|
|
94
101
|
width: auto; min-width: 112px; padding-right: 30px; appearance: none; cursor: pointer;
|
|
95
102
|
background-image: linear-gradient(45deg, transparent 50%, var(--t-dim) 50%), linear-gradient(135deg, var(--t-dim) 50%, transparent 50%);
|
|
@@ -167,18 +174,33 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
167
174
|
.fx-shell[data-folded] #user-button .cl-userButtonTrigger { width: auto; padding: 0; border-radius: 50%; background: transparent; box-shadow: none; }
|
|
168
175
|
.fx-shell[data-folded] #user-button .cl-userButtonOuterIdentifier, .fx-shell[data-folded] #user-button .cl-userButtonBox::before { display: none; }
|
|
169
176
|
}
|
|
170
|
-
.fx-main { flex: 1; min-width: 0;
|
|
177
|
+
.fx-main { flex: 1; min-width: 0; padding: 32px var(--fx-gutter) 64px; display: grid; gap: 20px; align-content: start; }
|
|
171
178
|
.fx-main:focus { outline: none; box-shadow: none; }
|
|
172
179
|
|
|
173
|
-
/* Page header: title
|
|
174
|
-
.
|
|
175
|
-
.fx-
|
|
180
|
+
/* Page header: the title only (no line under it), then the actions,
|
|
181
|
+
primary last. One height on every page, with or without a button. */
|
|
182
|
+
.fx-ph { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; min-height: 36px; }
|
|
183
|
+
.fx-ph__text { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
176
184
|
.fx-title {
|
|
177
185
|
color: var(--t-strong);
|
|
178
|
-
font: var(--t-display-weight)
|
|
186
|
+
font: var(--t-display-weight) 24px/1.15 var(--t-display); letter-spacing: -0.015em;
|
|
179
187
|
}
|
|
188
|
+
.fx-ph__count { color: var(--t-dim); font-size: 14px; font-variant-numeric: tabular-nums; }
|
|
189
|
+
/* A record's facts beside its title (Reconcile: client · date · total). */
|
|
180
190
|
.fx-ph__sub { color: var(--t-dim); }
|
|
181
|
-
|
|
191
|
+
/* A docs link as a "?" icon beside a title (helpLink in app.js). */
|
|
192
|
+
.fx-help { display: inline-flex; vertical-align: middle; margin-left: 2px; padding: 4px; border-radius: var(--t-radius); color: var(--t-dim); }
|
|
193
|
+
.fx-help:hover, .fx-help:focus-visible { background: var(--fx-hover); color: var(--t-strong); }
|
|
194
|
+
.fx-help .fx-lucide { width: 16px; height: 16px; }
|
|
195
|
+
.fx-ph__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
196
|
+
|
|
197
|
+
/* The tip an icon-only control shows on hover and keyboard focus (tip.js). */
|
|
198
|
+
.fx-tip {
|
|
199
|
+
position: fixed; top: 0; left: 0; z-index: 1000; max-width: 260px; padding: 4px 8px;
|
|
200
|
+
border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); color: var(--t-strong);
|
|
201
|
+
font: 500 12px/1.3 var(--t-body); pointer-events: none; white-space: nowrap;
|
|
202
|
+
box-shadow: var(--t-shadow), 0 4px 12px rgba(0, 0, 0, .12);
|
|
203
|
+
}
|
|
182
204
|
|
|
183
205
|
/* ── Notices, pills, tags, toasts ──────────────────────────────────────── */
|
|
184
206
|
.fx-notice {
|
|
@@ -287,10 +309,12 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
287
309
|
.fx-chip { border: 1px solid var(--fx-line); border-radius: 999px; background: var(--t-paper); color: var(--t-text); }
|
|
288
310
|
.fx-int-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; }
|
|
289
311
|
.fx-int-bar .fx-search { margin: 0; }
|
|
312
|
+
.fx-int-bar .fx-row { flex: 1 1 auto; flex-wrap: nowrap; justify-content: flex-end; min-width: 0; }
|
|
313
|
+
.fx-int-bar .fx-row .fx-search { flex: 0 1 320px; width: auto; min-width: 0; }
|
|
314
|
+
.fx-int-bar .fx-row .fx-select { flex: none; }
|
|
290
315
|
.fx-int-sections { display: grid; gap: 32px; }
|
|
291
316
|
.fx-int-section-list { display: grid; gap: 16px; }
|
|
292
317
|
.fx-int-heading { color: var(--t-strong); font: 600 19px/1.3 var(--t-body); }
|
|
293
|
-
.fx-int-heading-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
294
318
|
.fx-int-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
|
|
295
319
|
.fx-int-card {
|
|
296
320
|
display: grid; grid-template-rows: auto 1fr auto; gap: 14px; padding: 18px;
|
|
@@ -298,6 +322,7 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
298
322
|
color: var(--t-text); text-decoration: none;
|
|
299
323
|
transition: transform 140ms var(--fx-ease), box-shadow 140ms var(--fx-ease), border-color 140ms var(--fx-ease);
|
|
300
324
|
}
|
|
325
|
+
.fx-int-card--bare { grid-template-rows: auto auto; }
|
|
301
326
|
.fx-int-card:hover, .fx-int-card:focus-visible { transform: translateY(-2px); border-color: var(--t-brd-hi, var(--t-control)); box-shadow: 0 8px 24px rgba(0, 0, 0, .1); }
|
|
302
327
|
.fx-int-card__head { display: flex; align-items: center; gap: 12px; }
|
|
303
328
|
.fx-int-card__head h3 { flex: 1; color: var(--t-strong); font: 650 16px/1.25 var(--t-body); }
|
|
@@ -312,6 +337,26 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
312
337
|
display: grid; place-items: center; flex: none; width: 44px; height: 44px; border-radius: 10px;
|
|
313
338
|
background: #fff; box-shadow: inset 0 0 0 1px var(--fx-line);
|
|
314
339
|
}
|
|
340
|
+
.fx-logo--icon { background: var(--t-accent-bg); color: var(--t-accent); box-shadow: none; }
|
|
341
|
+
.fx-tiles { display: grid; gap: 12px; }
|
|
342
|
+
.fx-tiles + .fx-tiles { margin-top: 16px; }
|
|
343
|
+
.fx-tiles__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
344
|
+
.fx-tiles__head h2 { color: var(--t-strong); font: 600 15px/1.3 var(--t-body); }
|
|
345
|
+
.fx-tiles__more { display: inline-flex; align-items: center; gap: 4px; color: var(--t-dim); font-size: 13.5px; text-decoration: none; }
|
|
346
|
+
.fx-tiles__more:hover, .fx-tiles__more:focus-visible { color: var(--t-strong); }
|
|
347
|
+
.fx-tiles__more .fx-lucide { width: 15px; height: 15px; }
|
|
348
|
+
.fx-tiles__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 12px; }
|
|
349
|
+
.fx-tile {
|
|
350
|
+
display: grid; grid-template-columns: auto minmax(0, 1fr) auto; gap: 14px; align-items: center; padding: 12px 14px 12px 12px;
|
|
351
|
+
border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper); color: var(--t-text); text-decoration: none;
|
|
352
|
+
transition: border-color 140ms var(--fx-ease), background-color 140ms var(--fx-ease);
|
|
353
|
+
}
|
|
354
|
+
.fx-tile:hover, .fx-tile:focus-visible { border-color: var(--t-control); background: var(--fx-hover); }
|
|
355
|
+
.fx-tile .fx-logo { width: 48px; height: 48px; border-radius: var(--t-radius); }
|
|
356
|
+
.fx-tile .fx-logo--icon { background: var(--fx-hover); color: var(--t-strong); box-shadow: inset 0 0 0 1px var(--fx-line); }
|
|
357
|
+
.fx-tile h3 { overflow: hidden; color: var(--t-strong); font: 600 14.5px/1.35 var(--t-body); text-overflow: ellipsis; white-space: nowrap; }
|
|
358
|
+
.fx-tile__meta { color: var(--t-dim); font-size: 12.5px; white-space: nowrap; }
|
|
359
|
+
.fx-logo--icon .fx-lucide { width: 22px; height: 22px; }
|
|
315
360
|
.fx-logo img { width: 28px; height: 28px; object-fit: contain; }
|
|
316
361
|
.fx-logo--lg { width: 52px; height: 52px; }
|
|
317
362
|
.fx-logo--lg img { width: 34px; height: 34px; }
|
|
@@ -388,6 +433,28 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
388
433
|
.fx-tabs__count { padding: 0 7px; border-radius: 999px; background: var(--fx-hover); color: var(--t-dim); font-size: 12px; line-height: 20px; }
|
|
389
434
|
.fx-tabs button[data-tone="danger"] .fx-tabs__count { background: var(--t-danger-bg); color: var(--t-danger); }
|
|
390
435
|
|
|
436
|
+
/* Saved views (saved-views.js): the row of named view tabs over a list, as
|
|
437
|
+
ClickUp's. The chosen tab is underlined; its ⋯ shows on hover and when
|
|
438
|
+
chosen. Save and Revert sit at the right while the list differs. */
|
|
439
|
+
.fx-views { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; min-height: 32px; margin-bottom: -8px; }
|
|
440
|
+
.fx-views:empty { display: none; }
|
|
441
|
+
.fx-views__item { display: inline-flex; align-items: center; border-bottom: 2px solid transparent; }
|
|
442
|
+
.fx-views__item[data-on="true"] { border-bottom-color: var(--t-brand); }
|
|
443
|
+
.fx-views__tab { display: inline-flex; align-items: center; gap: 6px; max-width: 220px; padding: 6px 4px 6px 8px; border: 0; background: none; color: var(--t-dim); font: 600 13px var(--t-body); cursor: pointer; }
|
|
444
|
+
.fx-views__tab:hover, .fx-views__item[data-on="true"] .fx-views__tab { color: var(--t-strong); }
|
|
445
|
+
.fx-views__tab:focus-visible { outline: 2px solid var(--t-ring, var(--t-accent)); outline-offset: 1px; }
|
|
446
|
+
.fx-views__tab .fx-lucide { width: 14px; height: 14px; flex: none; }
|
|
447
|
+
.fx-views__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
448
|
+
.fx-views__dot { flex: none; width: 6px; height: 6px; border-radius: 50%; background: var(--t-accent); }
|
|
449
|
+
.fx-views__item .fx-cfm__more { width: 22px; height: 22px; opacity: 0; }
|
|
450
|
+
.fx-views__item:hover .fx-cfm__more, .fx-views__item[data-on="true"] .fx-cfm__more, .fx-views__item .fx-cfm__more[aria-expanded="true"], .fx-views__item .fx-cfm__more:focus-visible { opacity: 1; }
|
|
451
|
+
.fx-views__item .fx-cfm__more .fx-lucide { width: 14px; height: 14px; }
|
|
452
|
+
.fx-views__add { display: inline-flex; align-items: center; gap: 4px; padding: 6px 8px; border: 0; background: none; color: var(--t-dim); font: 500 13px var(--t-body); cursor: pointer; }
|
|
453
|
+
.fx-views__add:hover { color: var(--t-strong); }
|
|
454
|
+
.fx-views__add .fx-lucide { width: 14px; height: 14px; }
|
|
455
|
+
.fx-views__end { display: inline-flex; gap: 6px; margin-left: auto; }
|
|
456
|
+
@media (hover: none) { .fx-views__item .fx-cfm__more { opacity: 1; } }
|
|
457
|
+
|
|
391
458
|
/* Quiet alerts (server/ui/alerts.js): one slim line under the page header,
|
|
392
459
|
only while something on the page needs this person. */
|
|
393
460
|
.fx-alerts { display: flex; align-items: center; gap: 12px; margin: 0 0 12px; padding: 6px 6px 6px 14px; border: 1px solid var(--fx-line); border-left: 3px solid var(--t-warn); border-radius: var(--t-radius); background: var(--t-paper); font-size: 14px; }
|
|
@@ -473,7 +540,6 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
473
540
|
}
|
|
474
541
|
|
|
475
542
|
/* ── Jobs board ─────────────────────────────────────────────────────────── */
|
|
476
|
-
.fx-main--wide { max-width: none; }
|
|
477
543
|
.fx-main > * { min-width: 0; }
|
|
478
544
|
.fx-toolbar--plain { border-bottom: 0; align-items: center; }
|
|
479
545
|
.fx-toolbar--plain .fx-search { margin-bottom: 0; }
|
|
@@ -524,14 +590,16 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
524
590
|
.fx-chipset--on .fx-chip__clear { border-color: var(--t-accent-brd, var(--t-accent)); background: var(--t-accent-bg); color: var(--t-accent); }
|
|
525
591
|
/* Group by, Sort and Filters: rows of selects with a bin at the end, as
|
|
526
592
|
ClickUp lays them out, in Flow's own look. */
|
|
527
|
-
.fx-pop.fx-pop--rows { width: max-content; min-width: 360px; max-width: min(
|
|
593
|
+
.fx-pop.fx-pop--rows { width: max-content; min-width: 360px; max-width: min(760px, calc(100vw - 16px)); max-height: min(520px, calc(100vh - 32px)); padding: 12px; }
|
|
528
594
|
.fx-rows__title { margin: 0 0 10px; color: var(--t-strong); font: 600 14px/1.3 var(--t-body); }
|
|
529
595
|
.fx-rows { display: grid; gap: 8px; }
|
|
530
|
-
.fx-rows__row { display: grid; grid-template-columns:
|
|
596
|
+
.fx-rows__row { display: grid; grid-template-columns: 64px minmax(140px, 1fr) minmax(120px, auto) minmax(170px, 1.3fr) 32px; gap: 8px; align-items: start; }
|
|
531
597
|
.fx-rows__row--sort { grid-template-columns: 78px minmax(150px, 1fr) minmax(140px, auto) 32px; }
|
|
532
598
|
.fx-rows__row--group { grid-template-columns: minmax(170px, 1fr) minmax(130px, auto) 32px; }
|
|
533
599
|
.fx-rows .fx-select, .fx-rows .fx-input { width: 100%; min-width: 0; min-height: 32px; font-size: 13px; }
|
|
534
|
-
|
|
600
|
+
/* Rows align at the top, so a value on two lines (a date range) keeps the
|
|
601
|
+
bin beside its first line. */
|
|
602
|
+
.fx-rows__lead { display: flex; align-items: center; min-height: 32px; color: var(--t-dim); font: 500 13px/1.2 var(--t-body); }
|
|
535
603
|
.fx-rows__value { overflow: hidden; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
|
|
536
604
|
.fx-rows__gone { grid-column: span 3; color: var(--t-dim); font-size: 13px; font-style: italic; }
|
|
537
605
|
.fx-rows__bin {
|
|
@@ -545,13 +613,51 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
545
613
|
.fx-rows__foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 12px; }
|
|
546
614
|
.fx-rows__add { display: inline-flex; align-items: center; gap: 6px; }
|
|
547
615
|
.fx-rows__add .fx-lucide { width: 14px; height: 14px; }
|
|
616
|
+
/* The filter panel's groups, as ClickUp's: each group a shaded box with its
|
|
617
|
+
own rows and "Add nested filter"; groups stack with And / Or at the left. */
|
|
618
|
+
.fx-fgroup { display: grid; gap: 8px; padding: 10px; border-radius: var(--t-radius-lg); background: var(--fx-hover); }
|
|
619
|
+
.fx-fgroups { display: grid; gap: 8px; }
|
|
620
|
+
.fx-fgroups__row { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 8px; align-items: start; }
|
|
621
|
+
.fx-fgroups__row > .fx-rows__lead, .fx-fgroups__row > span { padding-top: 16px; }
|
|
622
|
+
.fx-fgroup__add { justify-self: start; padding: 2px 4px; border: 0; background: none; color: var(--t-dim); font: 500 12.5px/1.3 var(--t-body); cursor: pointer; }
|
|
623
|
+
.fx-fgroup__add:hover, .fx-fgroup__add:focus-visible { color: var(--t-strong); text-decoration: underline; outline: none; }
|
|
624
|
+
.fx-choice { display: inline-flex; align-items: center; gap: 6px; min-width: 0; text-align: left; }
|
|
625
|
+
.fx-choice .fx-lucide { flex: none; width: 14px; height: 14px; color: var(--t-dim); }
|
|
626
|
+
.fx-choice__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
627
|
+
.fx-rows .fx-rows__join { min-width: 0; width: 100%; padding-left: 8px; font: 600 12px/1 var(--t-data, var(--t-body)); letter-spacing: .04em; }
|
|
628
|
+
.fx-rows__word { font: 600 12px/1 var(--t-data, var(--t-body)); letter-spacing: .04em; padding-left: 9px; }
|
|
629
|
+
.fx-fgroups__row .fx-rows__join { min-width: 0; width: 64px; padding-left: 8px; font: 600 12px/1 var(--t-data, var(--t-body)); letter-spacing: .04em; min-height: 32px; }
|
|
630
|
+
.fx-rows__date { display: flex; flex-wrap: wrap; gap: 6px; overflow: visible; }
|
|
631
|
+
.fx-rows__date > .fx-choice { flex: 1 1 130px; }
|
|
632
|
+
.fx-rows__date .fx-input { width: auto; flex: 1 1 130px; }
|
|
633
|
+
.fx-rows__days { display: inline-flex; flex: 1 1 130px; align-items: center; gap: 6px; }
|
|
634
|
+
.fx-rows__days .fx-input[type="number"] { width: 72px; flex: none; }
|
|
635
|
+
.fx-pop__bar { display: flex; align-items: center; justify-content: space-between; }
|
|
636
|
+
.fx-pop__link { padding: 4px 8px; border: 0; background: none; color: var(--t-accent); font: 500 12.5px/1.3 var(--t-body); cursor: pointer; }
|
|
637
|
+
.fx-pop__link:hover, .fx-pop__link:focus-visible { text-decoration: underline; outline: none; }
|
|
548
638
|
.fx-pop.fx-pop--choices { min-width: 200px; max-width: 300px; padding: 6px; }
|
|
639
|
+
/* A value list row, as ClickUp's: mark (when there is one), name, tick box
|
|
640
|
+
at the right. No empty space at the left. */
|
|
641
|
+
.fx-pop__item--check { gap: 8px; }
|
|
642
|
+
.fx-pop__item--check .fx-pop__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
643
|
+
.fx-pop__box { display: inline-grid; place-items: center; flex: none; width: 16px; height: 16px; margin-left: auto; border: 1px solid var(--t-control); border-radius: var(--t-radius); color: var(--t-on-primary); }
|
|
644
|
+
.fx-pop__item--check[aria-checked="true"] .fx-pop__box { border-color: var(--t-primary); background: var(--t-primary); }
|
|
645
|
+
.fx-pop__box svg { width: 12px; height: 12px; }
|
|
646
|
+
.fx-dot { flex: none; width: 9px; height: 9px; border-radius: 50%; background: var(--cf, var(--t-dim)); }
|
|
647
|
+
.fx-dot--stage { --stage: var(--t-accent); background: var(--stage); }
|
|
648
|
+
.fx-dot--stage[data-rank="0"] { --stage: var(--t-mute); }
|
|
649
|
+
.fx-dot--stage[data-rank="3"], .fx-dot--stage[data-rank="4"] { --stage: color-mix(in srgb, var(--t-accent) 45%, var(--t-success)); }
|
|
650
|
+
.fx-dot--stage[data-rank="5"], .fx-dot--stage[data-rank="6"] { --stage: var(--t-success); }
|
|
651
|
+
.fx-dot--stage[data-rank="7"] { --stage: var(--t-dim); }
|
|
549
652
|
.fx-pop--choices .fx-input { min-height: 32px; margin-bottom: 4px; font-size: 13px; }
|
|
550
653
|
.fx-pop__end { display: inline-grid; margin-left: auto; color: var(--t-accent); }
|
|
551
654
|
@media (max-width: 640px) {
|
|
552
655
|
.fx-pop.fx-pop--rows { min-width: 0; width: calc(100vw - 16px); }
|
|
553
656
|
.fx-rows__row, .fx-rows__row--sort { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 32px; }
|
|
554
|
-
.fx-rows__row > .fx-rows__lead, .fx-rows__row > .fx-select:first-child { grid-column: 1 / -1; }
|
|
657
|
+
.fx-rows__row > .fx-rows__lead, .fx-rows__row > .fx-select:first-child, .fx-rows__row > .fx-rows__join { grid-column: 1 / -1; }
|
|
658
|
+
.fx-rows__row > .fx-rows__join { width: 72px; }
|
|
659
|
+
.fx-fgroups__row { grid-template-columns: minmax(0, 1fr); }
|
|
660
|
+
.fx-fgroups__row > .fx-rows__lead, .fx-fgroups__row > span { padding-top: 0; }
|
|
555
661
|
.fx-rows__row > .fx-rows__value { grid-column: 1 / 3; }
|
|
556
662
|
.fx-rows__row > .fx-rows__value--none { display: none; }
|
|
557
663
|
}
|
|
@@ -590,6 +696,42 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
590
696
|
.fx-th-sort .fx-lucide { width: 13px; height: 13px; }
|
|
591
697
|
.fx-th-sort__n { font: 700 10px/1 var(--t-body); }
|
|
592
698
|
@media (hover: none) { .fx-th-sort { opacity: 1; } }
|
|
699
|
+
/* A sized list (columns.js): each column has its width, the table is as
|
|
700
|
+
wide as its columns (never stretched; space left over stays empty) and
|
|
701
|
+
scrolls sideways past the page. A head's right
|
|
702
|
+
edge is a grip that resizes the column; a head drags to move it, and a
|
|
703
|
+
line shows where it lands. */
|
|
704
|
+
.fx-table-scroll { overflow-x: auto; scrollbar-width: thin; }
|
|
705
|
+
.fx-table.fx-table--sized { table-layout: fixed; }
|
|
706
|
+
.fx-table--sized th, .fx-table--sized td { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
707
|
+
.fx-table--sized th { position: relative; }
|
|
708
|
+
/* Every cell is one line: a long value ends in "…" and the row keeps its
|
|
709
|
+
height, as HubSpot's. A wrapper in a cell (a client with its warning
|
|
710
|
+
mark, a stage pill) shrinks its text, never its mark. */
|
|
711
|
+
.fx-table--sized td > * { max-width: 100%; }
|
|
712
|
+
.fx-table--sized .fx-job__cell { display: flex; min-width: 0; }
|
|
713
|
+
.fx-table--sized .fx-job__cell > * { flex: none; }
|
|
714
|
+
.fx-table--sized .fx-job__cell > .fx-strong,
|
|
715
|
+
.fx-table--sized .fx-status { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
716
|
+
.fx-table--sized .fx-job__cell > .fx-strong { flex: 0 1 auto; }
|
|
717
|
+
.fx-table--sized .fx-th-cell { max-width: 100%; }
|
|
718
|
+
.fx-table--sized .fx-th { overflow: hidden; text-overflow: ellipsis; }
|
|
719
|
+
.fx-th--drag .fx-th { cursor: grab; touch-action: none; }
|
|
720
|
+
.fx-th--drag[data-dragging] { opacity: .5; }
|
|
721
|
+
.fx-th--drag[data-dragging] .fx-th { cursor: grabbing; }
|
|
722
|
+
.fx-table--sized th[data-drop]::after { content: ""; position: absolute; top: 6px; bottom: 6px; width: 2px; border-radius: 1px; background: var(--t-accent); }
|
|
723
|
+
.fx-table--sized th[data-drop="before"]::after { left: 0; }
|
|
724
|
+
.fx-table--sized th[data-drop="after"]::after { right: 0; }
|
|
725
|
+
.fx-th-grip { position: absolute; top: 0; right: 0; bottom: 0; width: 8px; cursor: col-resize; touch-action: none; }
|
|
726
|
+
.fx-th-grip::after { content: ""; position: absolute; top: 25%; bottom: 25%; right: 3px; width: 2px; border-radius: 1px; background: transparent; }
|
|
727
|
+
th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
|
|
728
|
+
.fx-th-grip:hover::after, .fx-th-grip:focus-visible::after, .fx-table--sized[data-resizing] .fx-th-grip:active::after { background: var(--t-accent); }
|
|
729
|
+
.fx-th-grip:focus-visible { outline: none; }
|
|
730
|
+
.fx-table--sized[data-resizing] { cursor: col-resize; user-select: none; }
|
|
731
|
+
.fx-table--sized .fx-tick-cell, .fx-table--sized .fx-th-add, .fx-table--sized .fx-c-end { overflow: visible; text-overflow: clip; }
|
|
732
|
+
.fx-table--sized .fx-tick-cell { padding-right: 0; }
|
|
733
|
+
/* + stays at the right edge while the table scrolls sideways. */
|
|
734
|
+
.fx-table--sized .fx-th-add { position: sticky; right: 0; z-index: 1; padding-left: 0; padding-right: 12px; }
|
|
593
735
|
.fx-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
|
|
594
736
|
.fx-c-date time { font-variant-numeric: tabular-nums; }
|
|
595
737
|
.fx-stage[data-rank="plain"] { --stage: var(--t-dim); }
|
|
@@ -604,9 +746,41 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
604
746
|
.fx-board__head { display: flex; flex: none; align-items: center; gap: 8px; min-width: 0; padding: 2px 4px 4px 2px; }
|
|
605
747
|
.fx-board__sum { flex: none; margin-left: auto; color: var(--t-dim); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
606
748
|
.fx-board__cards { display: grid; gap: 8px; align-content: start; min-height: 0; max-height: 70vh; padding-right: 2px; overflow-y: auto; scrollbar-width: thin; }
|
|
749
|
+
/* listFrame() in kit.js: the window of a list or board (Jobs, Invoices,
|
|
750
|
+
Reconcile). Its body scrolls both ways; the foot holds the count. A table
|
|
751
|
+
or panel inside drops its own frame. On a wide, tall screen the page is
|
|
752
|
+
the window's height and does not scroll itself: the frame fills it, and
|
|
753
|
+
on the board each lane scrolls inside itself. */
|
|
754
|
+
.fx-frame { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper); box-shadow: var(--t-shadow); overflow: hidden; }
|
|
755
|
+
.fx-frame__view { display: flex; flex-direction: column; min-height: 0; }
|
|
756
|
+
.fx-frame__body { min-height: 0; overflow: auto; scrollbar-width: thin; }
|
|
757
|
+
/* edgeFade() in kit.js: a soft fade over each edge with more content past
|
|
758
|
+
it, in the colour under the rows (--fx-fade). */
|
|
759
|
+
.fx-fades { position: relative; --fx-fade: var(--t-paper); }
|
|
760
|
+
.fx-fade { position: absolute; z-index: 3; pointer-events: none; opacity: 0; transition: opacity .15s; }
|
|
761
|
+
.fx-fade[data-edge="top"] { inset: 0 0 auto; height: 24px; background: linear-gradient(to bottom, var(--fx-fade), transparent); }
|
|
762
|
+
.fx-fade[data-edge="bottom"] { inset: auto 0 0; height: 24px; background: linear-gradient(to top, var(--fx-fade), transparent); }
|
|
763
|
+
.fx-fade[data-edge="left"] { inset: 0 auto 0 0; width: 24px; background: linear-gradient(to right, var(--fx-fade), transparent); }
|
|
764
|
+
.fx-fade[data-edge="right"] { inset: 0 0 0 auto; width: 24px; background: linear-gradient(to left, var(--fx-fade), transparent); }
|
|
765
|
+
.fx-fades[data-fade~="top"] > .fx-fade[data-edge="top"], .fx-fades[data-fade~="bottom"] > .fx-fade[data-edge="bottom"],
|
|
766
|
+
.fx-fades[data-fade~="left"] > .fx-fade[data-edge="left"], .fx-fades[data-fade~="right"] > .fx-fade[data-edge="right"] { opacity: 1; }
|
|
767
|
+
.fx-main--board .fx-frame__view { --fx-fade: var(--t-bg); }
|
|
768
|
+
.fx-board__col.fx-fades { --fx-fade: var(--t-s1); }
|
|
769
|
+
.fx-board__col > .fx-fade[data-edge="bottom"] { border-radius: 0 0 var(--t-radius-lg) var(--t-radius-lg); }
|
|
770
|
+
.fx-frame__body .fx-panel { border: 0; border-radius: 0; box-shadow: none; overflow: visible; }
|
|
771
|
+
.fx-frame__body .fx-empty { padding: 24px; }
|
|
772
|
+
.fx-frame .fx-table-scroll { overflow: visible; }
|
|
773
|
+
.fx-frame thead th { position: sticky; top: 0; z-index: 2; }
|
|
774
|
+
.fx-frame__foot { display: flex; flex: none; align-items: center; justify-content: space-between; gap: 12px; min-height: 44px; padding: 6px 12px; border-top: 1px solid var(--fx-line); color: var(--t-dim); font-size: 13px; font-variant-numeric: tabular-nums; }
|
|
775
|
+
.fx-frame__tools { display: flex; align-items: center; gap: 8px; }
|
|
776
|
+
.fx-main--board .fx-frame__body { padding: 12px; background: var(--t-bg); }
|
|
607
777
|
@media (min-width: 821px) and (min-height: 560px) {
|
|
608
|
-
.fx-main--
|
|
609
|
-
.fx-main--
|
|
778
|
+
.fx-main--frame { height: 100vh; height: 100dvh; padding-bottom: 16px; display: flex; flex-direction: column; }
|
|
779
|
+
.fx-main--frame > * { flex: none; }
|
|
780
|
+
.fx-main--frame > .fx-frame { flex: 1; min-height: 0; }
|
|
781
|
+
.fx-main--frame .fx-frame__view, .fx-main--frame .fx-frame__body { flex: 1; }
|
|
782
|
+
.fx-main--board .fx-frame__body { display: flex; overflow: hidden; }
|
|
783
|
+
.fx-main--board .fx-jobs { flex: 1; min-width: 0; min-height: 0; }
|
|
610
784
|
.fx-main--board .fx-board { height: 100%; overflow-y: hidden; align-items: stretch; }
|
|
611
785
|
.fx-main--board .fx-board__cards { flex: 1; max-height: none; }
|
|
612
786
|
}
|
|
@@ -674,7 +848,14 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
674
848
|
the activity at the side. It stacks on a phone. */
|
|
675
849
|
.fx-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; color: var(--t-dim); overflow-wrap: anywhere; }
|
|
676
850
|
.fx-dialog.fx-task { width: min(1040px, calc(100vw - 32px)); max-height: calc(100vh - 48px); overflow-y: auto; }
|
|
677
|
-
.fx-task__grid { display: grid; grid-template-columns: minmax(0, 1fr)
|
|
851
|
+
.fx-task__grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; }
|
|
852
|
+
/* No sidecar: the main area takes the room. Fix is wider for the change table. */
|
|
853
|
+
.fx-task__grid:has(> .fx-sidecar[hidden]) { grid-template-columns: minmax(0, 1fr); }
|
|
854
|
+
.fx-task__grid:has(> .fx-sidecar [data-tab="fix"][aria-selected="true"]) { grid-template-columns: minmax(0, 1fr) 400px; }
|
|
855
|
+
.fx-task__side { background: var(--t-s1); }
|
|
856
|
+
.fx-task__tools { display: flex; align-items: center; gap: 4px; }
|
|
857
|
+
.fx-task__tools .fx-lucide { width: 16px; height: 16px; }
|
|
858
|
+
.fx-task__tools [aria-pressed="true"] { background: var(--fx-hover); color: var(--t-strong); }
|
|
678
859
|
.fx-task__main { min-width: 0; padding: 20px 28px 28px; }
|
|
679
860
|
.fx-task__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
|
680
861
|
.fx-task__crumb { display: flex; flex-wrap: wrap; gap: 6px; color: var(--t-dim); font-size: 12.5px; }
|
|
@@ -688,11 +869,22 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
688
869
|
.fx-task__late { color: var(--t-danger) !important; }
|
|
689
870
|
.fx-task__link { color: var(--t-accent); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--t-accent) 40%, transparent); }
|
|
690
871
|
.fx-task__link:hover { border-bottom-color: currentColor; }
|
|
691
|
-
.fx-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
.fx-
|
|
695
|
-
.fx-
|
|
872
|
+
.fx-task__lines h3 { margin: 0; color: var(--t-dim); font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
873
|
+
/* What to fix: one toggle chip a problem beside the stage; its problem opens
|
|
874
|
+
in the sidecar's Fix tab (jobs.js openJob). One layout with or without. */
|
|
875
|
+
.fx-task__stage { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
876
|
+
.fx-task__fixchip { all: unset; cursor: pointer; display: inline-flex; align-items: center; gap: 6px; height: 24px; padding: 0 10px; border: 1px solid var(--t-warn-brd, var(--t-warn)); border-radius: 999px; background: var(--t-warn-bg); color: var(--t-warn); font-size: 12.5px; font-weight: 600; }
|
|
877
|
+
.fx-task__fixchip .fx-lucide { width: 14px; height: 14px; }
|
|
878
|
+
.fx-task__fixchip:hover { border-color: var(--t-warn); }
|
|
879
|
+
.fx-task__fixchip:focus-visible { box-shadow: var(--fx-focus); }
|
|
880
|
+
/* Pressed: its problem is open in the sidecar's Fix tab. */
|
|
881
|
+
.fx-task__fixchip[aria-pressed="true"] { border-color: var(--t-warn); background: var(--t-warn); color: var(--t-paper); }
|
|
882
|
+
.fx-task__fixitem { display: grid; gap: 8px; justify-items: start; font-size: 13.5px; }
|
|
883
|
+
.fx-task__fixitem > strong { color: var(--t-strong); }
|
|
884
|
+
.fx-task__fixitem > p { margin: 0; }
|
|
885
|
+
.fx-task__fixitem > .fx-task__review { justify-self: stretch; }
|
|
886
|
+
.fx-task__fixgo .fx-lucide { width: 14px; height: 14px; }
|
|
887
|
+
.fx-task__fixgo { display: inline-flex; align-items: center; gap: 6px; }
|
|
696
888
|
.fx-task__choose { scroll-margin-top: 16px; margin-top: 22px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px 14px; border: 1px solid var(--t-warn-brd, var(--t-warn)); border-radius: var(--t-radius); background: var(--t-warn-bg); color: var(--t-strong); font-size: 13.5px; }
|
|
697
889
|
.fx-task__choose > svg { flex: none; width: 20px; height: 20px; color: var(--t-warn); }
|
|
698
890
|
.fx-task__choose > span { flex: 1 1 200px; }
|
|
@@ -704,7 +896,6 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
704
896
|
invoice opened from Invoices (the pager on top). */
|
|
705
897
|
.fx-rec { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
706
898
|
.fx-rec .fx-lucide { width: 16px; height: 16px; }
|
|
707
|
-
.fx-rec__gap { max-width: 1180px; }
|
|
708
899
|
.fx-rec__gap .fx-toolbar .fx-rows__title { margin: 0; }
|
|
709
900
|
.fx-rec__gap .fx-ph__actions .fx-btn, .fx-rec__acts .fx-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
|
|
710
901
|
.fx-rec__results { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
@@ -758,8 +949,9 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
758
949
|
.fx-task__miss { color: var(--t-warn); }
|
|
759
950
|
.fx-task__actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 20px; }
|
|
760
951
|
.fx-task__actions:empty { display: none; }
|
|
761
|
-
.fx-task__activity { min-width: 0;
|
|
762
|
-
|
|
952
|
+
.fx-task__activity { min-width: 0; }
|
|
953
|
+
/* Review in the sidecar: the change banner, without its own box. */
|
|
954
|
+
.fx-task__review .fx-change { margin: 0; padding: 0; border: 0; background: none; }
|
|
763
955
|
.fx-task__activity ol { margin: 0; padding: 0; list-style: none; }
|
|
764
956
|
.fx-task__activity li { position: relative; padding: 0 0 16px 18px; color: var(--t-text); font-size: 13px; }
|
|
765
957
|
.fx-task__activity li::before { content: ""; position: absolute; left: 3px; top: 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--t-mute); }
|
|
@@ -771,7 +963,8 @@ th:hover .fx-th-sort, .fx-th-sort:focus-visible, .fx-th-sort--on { opacity: 1; }
|
|
|
771
963
|
.fx-task__grid { grid-template-columns: minmax(0, 1fr); }
|
|
772
964
|
.fx-task__main { padding: 16px; }
|
|
773
965
|
.fx-task__fields { grid-template-columns: minmax(0, 1fr); }
|
|
774
|
-
.fx-
|
|
966
|
+
.fx-task__grid:has(> .fx-sidecar [data-tab="fix"][aria-selected="true"]) { grid-template-columns: minmax(0, 1fr); }
|
|
967
|
+
.fx-task__side { border-top: 1px solid var(--fx-line); border-left: 0; }
|
|
775
968
|
}
|
|
776
969
|
|
|
777
970
|
/* ── Dialog ─────────────────────────────────────────────────────────────── */
|
|
@@ -898,14 +1091,21 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
898
1091
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15);
|
|
899
1092
|
}
|
|
900
1093
|
.fx-swatch[aria-checked="true"] { box-shadow: 0 0 0 2px var(--t-paper), 0 0 0 4px var(--t-text); }
|
|
901
|
-
|
|
902
|
-
.fx-swatch--default
|
|
1094
|
+
/* The default accent: a pill that says Default, before the colours. */
|
|
1095
|
+
.fx-swatch--default {
|
|
1096
|
+
display: inline-flex; align-items: center; gap: 6px; width: auto; padding: 0 10px 0 4px; border-radius: 999px;
|
|
1097
|
+
background: var(--t-paper); color: var(--t-text); font: 600 12.5px/1 var(--t-body); box-shadow: inset 0 0 0 1px var(--t-control);
|
|
1098
|
+
}
|
|
1099
|
+
.fx-swatch--default[aria-checked="true"] { box-shadow: inset 0 0 0 1px var(--t-control), 0 0 0 2px var(--t-paper), 0 0 0 4px var(--t-text); }
|
|
1100
|
+
.fx-swatch__dot { flex: none; width: 22px; height: 22px; border-radius: 50%; }
|
|
903
1101
|
.fx-swatch-custom { display: inline-flex; align-items: center; gap: 8px; color: var(--t-dim); font-size: 13px; font-weight: 600; }
|
|
904
1102
|
.fx-swatch-custom input { width: 34px; height: 30px; padding: 0; border: 1px solid var(--t-control); border-radius: var(--t-radius); background: var(--t-paper); cursor: pointer; }
|
|
905
1103
|
|
|
906
1104
|
/* ── Narrow screens: the sidebar becomes a bar across the top. ──────────── */
|
|
907
1105
|
@media (max-width: 820px) {
|
|
908
1106
|
.fx-shell { flex-direction: column; }
|
|
1107
|
+
/* In a column, the page takes the full width, not its content's. */
|
|
1108
|
+
.fx-main { width: 100%; }
|
|
909
1109
|
.fx-side { flex-direction: row; align-items: center; width: auto; height: 60px; padding: 0 12px; border-right: 0; border-bottom: 1px solid var(--fx-line); }
|
|
910
1110
|
.fx-side__head { flex-direction: row-reverse; gap: 6px; padding: 0; }
|
|
911
1111
|
.fx-side__head .fx-brand { display: none; }
|
|
@@ -946,8 +1146,11 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
946
1146
|
/* ── Phones ─────────────────────────────────────────────────────────────── */
|
|
947
1147
|
@media (max-width: 640px) {
|
|
948
1148
|
.fx-main { padding-top: 20px; }
|
|
949
|
-
.fx-title { font-size:
|
|
1149
|
+
.fx-title { font-size: 22px; }
|
|
950
1150
|
.fx-table thead { display: none; }
|
|
1151
|
+
.fx-table.fx-table--sized { width: 100% !important; min-width: 0; table-layout: auto; }
|
|
1152
|
+
.fx-table--sized td { white-space: normal; }
|
|
1153
|
+
.fx-table--sized colgroup { display: none; }
|
|
951
1154
|
.fx-table, .fx-table tbody, .fx-table tr, .fx-table td { display: block; width: 100%; }
|
|
952
1155
|
.fx-table tr { padding: 12px 14px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
953
1156
|
.fx-table td { padding: 4px 0; border: 0; }
|
|
@@ -955,7 +1158,6 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
955
1158
|
.fx-table tbody tr:hover td { background: none; }
|
|
956
1159
|
.fx-table__actions { text-align: left; }
|
|
957
1160
|
.fx-table__actions .fx-row--end { justify-content: flex-start; }
|
|
958
|
-
.fx-ph__actions, .fx-ph__actions .fx-btn { width: 100%; }
|
|
959
1161
|
/* People: the name with its actions (⋯, Approve) on one line, then the
|
|
960
1162
|
role select full width, then the status pill when there is one. */
|
|
961
1163
|
.fx-people tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px 10px; }
|
|
@@ -976,7 +1178,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
976
1178
|
.fx-table--jobs tr.fx-row-link > td.fx-c-client { grid-area: 1 / 2; }
|
|
977
1179
|
.fx-table--jobs tr.fx-row-link > td.fx-c-total { grid-area: 1 / 3; }
|
|
978
1180
|
.fx-table--jobs td.fx-c-end { display: none; }
|
|
979
|
-
.fx-table--jobs tr.fx-row-link > td[data-label]:not(.fx-c-total, .fx-c-stage)::before { content: attr(data-label) " "; color: var(--t-dim); font-size: 12px; }
|
|
1181
|
+
.fx-table--jobs tr.fx-row-link > td[data-label]:not(.fx-c-client, .fx-c-total, .fx-c-stage)::before { content: attr(data-label) " "; color: var(--t-dim); font-size: 12px; }
|
|
980
1182
|
.fx-table--jobs tr.fx-table__group { grid-template-columns: 28px minmax(0, 1fr); background: var(--t-s1); }
|
|
981
1183
|
.fx-table--jobs tr.fx-table__group > td.fx-tick-cell { grid-row: 1; align-self: center; padding: 0; }
|
|
982
1184
|
.fx-table--jobs tr.fx-table__group > td { background: none; }
|
|
@@ -1118,10 +1320,22 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1118
1320
|
in from the right of the manager and leaves the table usable. */
|
|
1119
1321
|
.fx-cfm__card { position: relative; }
|
|
1120
1322
|
.fx-cfm__card:has(> .fx-cfs:not([hidden])) { min-height: min(640px, 100vh); }
|
|
1121
|
-
|
|
1122
|
-
.
|
|
1123
|
-
|
|
1124
|
-
|
|
1323
|
+
/* The shared sidecar (sidecar.js): a right column, or over the page with
|
|
1324
|
+
--over. Tabs are an icon and one word; × closes it. */
|
|
1325
|
+
.fx-sidecar { display: flex; flex-direction: column; min-width: 0; border-left: 1px solid var(--fx-line); background: var(--t-paper); color: var(--t-text); }
|
|
1326
|
+
.fx-sidecar[hidden] { display: none; }
|
|
1327
|
+
.fx-sidecar--over { position: absolute; inset: 0 0 0 auto; z-index: 5; width: min(420px, 100%); box-shadow: -16px 0 40px rgba(0, 0, 0, .22); animation: fx-sidecar-in .18s var(--fx-ease); }
|
|
1328
|
+
@keyframes fx-sidecar-in { from { transform: translateX(24px); opacity: 0; } }
|
|
1329
|
+
@media (prefers-reduced-motion: reduce) { .fx-sidecar--over { animation: none; } }
|
|
1330
|
+
.fx-sidecar__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 52px; padding: 0 12px 0 8px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
1331
|
+
.fx-sidecar__tabs { display: flex; gap: 2px; min-width: 0; }
|
|
1332
|
+
.fx-sidecar__tab { all: unset; cursor: pointer; position: relative; display: inline-flex; align-items: center; gap: 6px; min-height: 30px; padding: 0 10px; border-radius: var(--t-radius); color: var(--t-dim); font-size: 13px; font-weight: 500; }
|
|
1333
|
+
.fx-sidecar__tab .fx-lucide { width: 15px; height: 15px; }
|
|
1334
|
+
.fx-sidecar__tab:hover { background: var(--fx-hover); color: var(--t-strong); }
|
|
1335
|
+
.fx-sidecar__tab[aria-selected="true"] { background: var(--fx-hover); color: var(--t-strong); }
|
|
1336
|
+
.fx-sidecar__tab:focus-visible { box-shadow: var(--fx-focus); }
|
|
1337
|
+
.fx-sidecar__mark { width: 6px; height: 6px; border-radius: 50%; background: var(--t-warn); }
|
|
1338
|
+
.fx-sidecar__body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; }
|
|
1125
1339
|
.fx-cfs__form { position: sticky; top: 0; display: flex; flex-direction: column; width: 100%; height: min(100%, 100vh); max-height: 100vh; min-height: 0; }
|
|
1126
1340
|
.fx-cfs__head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 52px; padding: 0 12px 0 16px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
1127
1341
|
.fx-cfs__typename, .fx-cfs__typepick { display: inline-flex; align-items: center; gap: 8px; color: var(--t-text); font-weight: 500; font-size: 14px; }
|
|
@@ -1129,9 +1343,9 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1129
1343
|
.fx-cfs__typepick:hover, .fx-cfs__typepick:focus-within { background: var(--fx-hover); }
|
|
1130
1344
|
.fx-cfs__typepick .fx-lucide { width: 15px; height: 15px; }
|
|
1131
1345
|
.fx-cfs__type { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
|
|
1132
|
-
.fx-
|
|
1133
|
-
.fx-
|
|
1134
|
-
.fx-
|
|
1346
|
+
.fx-sidecar__close, .fx-cfs__delete, .fx-cfs__tool { all: unset; cursor: pointer; display: inline-grid; place-items: center; width: 30px; height: 30px; border-radius: 6px; color: var(--t-dim); }
|
|
1347
|
+
.fx-sidecar__close:hover, .fx-cfs__tool:hover:not(:disabled) { background: var(--fx-hover); color: var(--t-strong); }
|
|
1348
|
+
.fx-sidecar__close:focus-visible, .fx-cfs__delete:focus-visible, .fx-cfs__tool:focus-visible { box-shadow: var(--fx-focus); }
|
|
1135
1349
|
.fx-cfs__body { flex: 1; min-height: 0; overflow-y: auto; display: grid; align-content: start; gap: 0; }
|
|
1136
1350
|
.fx-cfs__section { display: grid; gap: 8px; padding: 14px 16px 4px; }
|
|
1137
1351
|
.fx-cfs__section:last-child { margin-top: 14px; padding-top: 16px; border-top: 1px solid var(--fx-line-soft); }
|
|
@@ -1167,7 +1381,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1167
1381
|
.fx-cfs__where .fx-lucide { width: 16px; height: 16px; color: var(--t-dim); }
|
|
1168
1382
|
.fx-cfs__foot { display: flex; align-items: center; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--fx-line-soft); }
|
|
1169
1383
|
.fx-cfs__delete { color: var(--t-danger); background: color-mix(in srgb, var(--t-danger) 12%, transparent); }
|
|
1170
|
-
.fx-cfs__delete:hover { background: color-mix(in srgb, var(--t-danger)
|
|
1384
|
+
.fx-cfs__delete:hover { background: color-mix(in srgb, var(--t-danger) 28%, transparent); }
|
|
1171
1385
|
.fx-cfs__delete .fx-lucide { width: 16px; height: 16px; }
|
|
1172
1386
|
@media (max-width: 720px) {
|
|
1173
1387
|
.fx-cfm { grid-template-columns: minmax(0, 1fr) 36px 32px; column-gap: 8px; }
|
|
@@ -1250,13 +1464,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1250
1464
|
.fx-pager__step:hover:not(:disabled) { background: var(--fx-hover); border-color: var(--fx-line); }
|
|
1251
1465
|
.fx-pager__step:disabled { opacity: 0.35; cursor: default; }
|
|
1252
1466
|
|
|
1253
|
-
/* New invoice from a job: a
|
|
1254
|
-
list of jobs with no invoice. */
|
|
1255
|
-
.fx-fromjob {
|
|
1256
|
-
display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px 16px;
|
|
1257
|
-
padding: 12px 16px; border: 1px dashed var(--fx-line); border-radius: var(--t-radius);
|
|
1258
|
-
}
|
|
1259
|
-
.fx-fromjob__text { display: grid; gap: 2px; font-size: 14px; }
|
|
1467
|
+
/* New invoice from a job: a searchable list of jobs with no invoice. */
|
|
1260
1468
|
.fx-dialog:has(.fx-pick__body) { width: min(620px, calc(100vw - 32px)); }
|
|
1261
1469
|
.fx-pick__body { display: grid; gap: 12px; }
|
|
1262
1470
|
.fx-pick { display: grid; max-height: min(420px, 55vh); overflow-y: auto; border: 1px solid var(--fx-line); border-radius: var(--t-radius); }
|
|
@@ -1270,12 +1478,21 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1270
1478
|
/* The Invoices list head (invoicing.js): tabs, search and the send bar stay
|
|
1271
1479
|
at the top while rows scroll up under them; a shadow shows once they do.
|
|
1272
1480
|
Narrow, the app bar (60px) sits above it. */
|
|
1273
|
-
|
|
1481
|
+
/* listHead() in kit.js: the toolbar row of every list. One height, with
|
|
1482
|
+
chips, tabs or a Board/List switch in it, so the rows start at one place. */
|
|
1483
|
+
.fx-listhead > .fx-toolbar { min-height: 40px; }
|
|
1484
|
+
.fx-listhead .fx-toolbar__end { flex: 1 1 auto; }
|
|
1485
|
+
.fx-listhead > .fx-toolbar--plain { align-items: center; }
|
|
1486
|
+
.fx-listhead[data-sticky] { position: sticky; top: 0; z-index: 5; margin: -12px calc(-1 * var(--fx-gutter)) 0; padding: 12px var(--fx-gutter) 0; background: var(--t-bg); transition: box-shadow .15s; }
|
|
1274
1487
|
.fx-listhead[data-stuck] { box-shadow: 0 10px 12px -12px rgba(0, 0, 0, .45); }
|
|
1275
|
-
|
|
1276
|
-
|
|
1488
|
+
/* The edge tells listHead when the rows reach the toolbar. In the page's
|
|
1489
|
+
grid it takes a row and a gap of its own, so the toolbar moves up by both. */
|
|
1490
|
+
.fx-listhead__edge { height: 0; }
|
|
1491
|
+
.fx-listhead__edge + .fx-listhead[data-sticky] { margin-top: -32px; }
|
|
1492
|
+
.fx-listhead > .fx-toolbar > .fx-tabs button { padding-block: 9px; }
|
|
1493
|
+
@media (max-width: 820px) { .fx-listhead[data-sticky] { top: 60px; } }
|
|
1277
1494
|
/* On a phone the head would hide too many rows: it scrolls away. */
|
|
1278
|
-
@media (max-width: 560px) { .fx-listhead { position: static; } }
|
|
1495
|
+
@media (max-width: 560px) { .fx-listhead[data-sticky] { position: static; } }
|
|
1279
1496
|
|
|
1280
1497
|
/* The bulk action bar (bulk-bar.js): floats at the foot while rows are
|
|
1281
1498
|
ticked. "N selected ×", then icon actions in groups, then More. It takes
|
|
@@ -1300,12 +1517,6 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1300
1517
|
.fx-bulk__act[data-tone="danger"] { color: var(--t-danger); }
|
|
1301
1518
|
.fx-bulk__act[data-tone="danger"]:hover { background: var(--t-danger-bg); }
|
|
1302
1519
|
.fx-bulk__sep { flex: none; width: 1px; height: 18px; margin: 0 4px; background: var(--fx-line); }
|
|
1303
|
-
/* An icon-only action says its name on hover, in a tip above it. */
|
|
1304
|
-
.fx-bulk [data-tip]:hover::after, .fx-bulk [data-tip]:focus-visible::after {
|
|
1305
|
-
content: attr(data-tip); position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
|
|
1306
|
-
padding: 4px 8px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); color: var(--t-strong);
|
|
1307
|
-
font: 500 12px/1.2 var(--t-body); pointer-events: none; box-shadow: var(--t-shadow), 0 4px 12px rgba(0, 0, 0, .12);
|
|
1308
|
-
}
|
|
1309
1520
|
/* More: a compact list, one row per action, like a dropdown. */
|
|
1310
1521
|
.fx-pop.fx-bulk__more { width: 248px; min-width: 0; padding: 4px; }
|
|
1311
1522
|
.fx-bulk__search { width: 100%; height: 30px; margin-bottom: 2px; padding: 4px 8px; font-size: 13px; }
|
|
@@ -1326,7 +1537,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1326
1537
|
/* Xero › Import (server/ui/xero-import.js): Review compares one fact per row
|
|
1327
1538
|
with ✓ or ≠ between the sides; the list and history tick rows for the bulk
|
|
1328
1539
|
bar. */
|
|
1329
|
-
.fx-xi { display: flex; flex-direction: column; gap: 16px; min-width: 0;
|
|
1540
|
+
.fx-xi { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
1330
1541
|
.fx-xi .fx-lucide { width: 16px; height: 16px; }
|
|
1331
1542
|
.fx-xi__views { display: inline-flex; border: 1px solid var(--fx-line); }
|
|
1332
1543
|
.fx-xi__view { display: inline-grid; place-items: center; width: 40px; height: 36px; border: 0; background: transparent; color: var(--t-dim); cursor: pointer; }
|
|
@@ -1370,6 +1581,8 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1370
1581
|
|
|
1371
1582
|
/* A list's own search (view-bar.js findBox): an icon that opens into a box. */
|
|
1372
1583
|
.fx-find { display: inline-flex; align-items: center; }
|
|
1584
|
+
/* On a tab bar the icon sits level with the tab names, not on the line. */
|
|
1585
|
+
.fx-toolbar > .fx-find { align-self: center; }
|
|
1373
1586
|
.fx-find__button { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; }
|
|
1374
1587
|
.fx-find__button .fx-lucide { width: 15px; height: 15px; }
|
|
1375
1588
|
.fx-find__input { display: none; width: min(240px, 60vw); height: 32px; padding: 0 10px; }
|
|
@@ -18,9 +18,11 @@ export function sendModeNote(to, sendMode, what = 'each invoice') {
|
|
|
18
18
|
return text.join(' ');
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// A warning above a draft, only when its Send cannot work: the accounting
|
|
22
|
+
// system is off or takes no invoices. Otherwise nothing: the problems show
|
|
23
|
+
// on their own, and the Send dialog says what Send does.
|
|
24
|
+
export function draftNote(to, invoice) {
|
|
25
|
+
const sendMode = invoice?.sendMode;
|
|
26
|
+
if (!to || !sendMode || (sendMode.connected && sendMode.writesAllowed)) return null;
|
|
27
|
+
return sendModeNote(to, sendMode);
|
|
26
28
|
}
|
|
@@ -12,10 +12,10 @@ const TEST_LABELS = {
|
|
|
12
12
|
more_than: 'is more than', at_most: 'is at most', is: 'is', is_not: 'is not', is_one_of: 'is one of',
|
|
13
13
|
is_empty: 'is empty', is_not_empty: 'is not empty', has: 'has', has_not: 'does not have',
|
|
14
14
|
is_not_one_of: 'is not one of', contains: 'contains', does_not_contain: 'does not contain',
|
|
15
|
-
starts_with: 'starts with', does_not_start_with: 'does not start with', has_number: 'has a number', one_word: 'is one word',
|
|
15
|
+
starts_with: 'starts with', does_not_start_with: 'does not start with', has_number: 'has a number', number_in_words: 'has a number beside words', one_word: 'is one word',
|
|
16
16
|
};
|
|
17
17
|
const ACTION_LABELS = { wait: 'Wait for a person', skip: 'Never draft', pending: 'Pending, with a note' };
|
|
18
|
-
const NO_VALUE = ['is_empty', 'is_not_empty', 'has_number', 'one_word'];
|
|
18
|
+
const NO_VALUE = ['is_empty', 'is_not_empty', 'has_number', 'number_in_words', 'one_word'];
|
|
19
19
|
const MANY = ['is_one_of', 'is_not_one_of'];
|
|
20
20
|
const TYPED = ['contains', 'does_not_contain', 'starts_with', 'does_not_start_with'];
|
|
21
21
|
|
|
@@ -158,7 +158,7 @@ export function rulesSection(ui, title = 'Rules') {
|
|
|
158
158
|
// A pending rule's note: what a person must fix. It shows on the draft.
|
|
159
159
|
const note = h('input', {
|
|
160
160
|
class: 'fx-input', maxlength: '200', value: draft?.action.note ?? '', 'aria-label': 'Note',
|
|
161
|
-
placeholder: '
|
|
161
|
+
placeholder: 'Type a note',
|
|
162
162
|
});
|
|
163
163
|
const noteField = h('div', { class: 'fx-field' }, h('span', { class: 'fx-label' }, 'Note on the draft'), note);
|
|
164
164
|
const showNote = () => { noteField.hidden = action.value !== 'pending'; };
|