@arcforgelabs/flow 2026.10.36 → 2026.10.38
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 +2 -2
- package/package.json +1 -1
- package/server/api/custom-fields.js +2 -1
- package/server/api/invoicing.js +61 -3
- package/server/auth/clerk.js +35 -0
- package/server/auth/routes.js +45 -6
- package/server/core/custom-fields.js +18 -0
- package/server/core/host.js +24 -0
- package/server/core/job-board.js +2 -1
- package/server/core/job-match.js +241 -0
- package/server/core/ledger.js +80 -1
- package/server/index.js +9 -3
- package/server/integrations/seazona/feed.js +4 -1
- package/server/integrations/seazona/index.js +1 -1
- package/server/integrations/seazona/settings.js +5 -2
- package/server/integrations/sentry/index.js +5 -2
- package/server/integrations/xero/fit.js +108 -0
- package/server/integrations/xero/import.js +6 -1
- package/server/integrations/xero/index.js +4 -4
- package/server/integrations/xero/ledger.js +3 -0
- package/server/integrations/xero/links.js +293 -0
- package/server/integrations/xero/migrations/0008_xero_invoice_links.sql +26 -0
- package/server/integrations/xero/migrations/meta/_journal.json +7 -0
- package/server/integrations/xero/reconcile.js +17 -11
- package/server/integrations/xero/routes.js +42 -0
- package/server/integrations/xero/settings.js +12 -5
- package/server/ui/app-page.js +1 -1
- package/server/ui/app.js +142 -23
- package/server/ui/bulk-bar.js +100 -0
- package/server/ui/flow.css +225 -11
- package/server/ui/invoicing.js +234 -30
- package/server/ui/job-board.js +60 -34
- package/server/ui/job-view.js +30 -7
- package/server/ui/jobs.js +469 -38
- package/server/ui/reconcile.js +275 -0
- package/server/ui/routes.js +1 -1
- package/server/ui/xero-import.js +329 -0
package/server/ui/flow.css
CHANGED
|
@@ -155,7 +155,8 @@ body:is([data-state="off"], [data-state="error"], [data-state="no-access"]) .fx-
|
|
|
155
155
|
/* Folded (wide screens only): the sidebar keeps its icons, the menu button
|
|
156
156
|
and the account photo; each page's name is still read out. */
|
|
157
157
|
@media (min-width: 821px) {
|
|
158
|
-
.fx-shell[data-folded] .fx-side { width: 64px; padding-inline: 10px; }
|
|
158
|
+
.fx-shell[data-folded] .fx-side { width: 64px; padding-inline: 10px; cursor: e-resize; }
|
|
159
|
+
.fx-shell[data-folded] .fx-side :is(a, button, #user-button) { cursor: pointer; }
|
|
159
160
|
.fx-shell[data-folded] .fx-side__head { flex-direction: column; gap: 10px; padding: 0 0 14px; }
|
|
160
161
|
.fx-shell[data-folded] .fx-side__head .fx-brand { display: none; }
|
|
161
162
|
.fx-shell[data-folded] .fx-nav__group { height: 1px; margin: 10px 6px; padding: 0; overflow: hidden; background: var(--fx-line); color: transparent; }
|
|
@@ -234,6 +235,12 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
234
235
|
.fx-link__dot[data-tone="success"] { background: var(--t-success); }
|
|
235
236
|
.fx-link__dot[data-tone="warn"] { background: var(--t-warn); }
|
|
236
237
|
.fx-link__dot[data-tone="danger"] { background: var(--t-danger); }
|
|
238
|
+
.fx-linked { display: grid; }
|
|
239
|
+
.fx-linked__row { display: flex; align-items: center; gap: 8px; white-space: nowrap; min-height: 44px; padding: 6px 0; border-top: 1px solid var(--fx-line-soft); }
|
|
240
|
+
.fx-linked__row:first-child { border-top: 0; }
|
|
241
|
+
.fx-linked__ref { font-family: var(--t-data); font-variant-numeric: tabular-nums; color: var(--t-strong); }
|
|
242
|
+
.fx-linked__open { margin-left: auto; font-size: 12.5px; color: var(--t-dim); white-space: nowrap; }
|
|
243
|
+
.fx-linked__open:hover { color: var(--t-strong); }
|
|
237
244
|
.fx-tag {
|
|
238
245
|
margin-left: 8px; padding: 1px 7px; border: 1px solid var(--fx-line); border-radius: 999px;
|
|
239
246
|
color: var(--t-dim); font-size: 11.5px; font-weight: 600;
|
|
@@ -269,9 +276,6 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
269
276
|
font-size: 12.5px; font-weight: 700; letter-spacing: .02em;
|
|
270
277
|
}
|
|
271
278
|
.fx-empty { padding: 28px 16px; color: var(--t-dim); text-align: center; }
|
|
272
|
-
.fx-roles { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; margin: 0; font-size: 13.5px; color: var(--t-dim); }
|
|
273
|
-
.fx-roles dt { color: var(--t-strong); font-weight: 600; }
|
|
274
|
-
.fx-roles dd { margin: 0; }
|
|
275
279
|
|
|
276
280
|
/* ── Integrations ───────────────────────────────────────────────────────── */
|
|
277
281
|
.fx-chip { border: 1px solid var(--fx-line); border-radius: 999px; background: var(--t-paper); color: var(--t-text); }
|
|
@@ -508,13 +512,32 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
508
512
|
.fx-table tbody tr.fx-table__group:hover td { background: var(--t-s1); }
|
|
509
513
|
.fx-table__group-row { display: flex; align-items: center; gap: 12px; }
|
|
510
514
|
.fx-table__group-row .fx-board__sum { margin-left: 0; }
|
|
515
|
+
/* The Jobs list's group head folds its rows (jobs.js, list): a click on the
|
|
516
|
+
head or its chevron. */
|
|
517
|
+
.fx-table--jobs .fx-table__group { cursor: pointer; }
|
|
518
|
+
.fx-table--jobs .fx-table__group td.fx-tick-cell { padding-right: 0; }
|
|
519
|
+
.fx-table__fold { display: inline-flex; align-items: center; gap: 8px; min-width: 0; padding: 2px 4px 2px 0; border: 0; background: none; color: var(--t-dim); font: inherit; cursor: pointer; }
|
|
520
|
+
.fx-table__fold > svg { flex: none; transition: transform .15s; }
|
|
521
|
+
.fx-table__fold[aria-expanded="true"] > svg { transform: rotate(90deg); }
|
|
522
|
+
.fx-table__fold:hover, .fx-table__fold:focus-visible { color: var(--t-strong); outline: none; }
|
|
523
|
+
.fx-table__fold:focus-visible .fx-stage, .fx-table__fold:focus-visible .fx-cf__opt { box-shadow: 0 0 0 2px var(--t-accent); }
|
|
524
|
+
.fx-table__group--folded td { padding-bottom: 14px; }
|
|
525
|
+
/* A Jobs list column head opens the column's menu (a click or a
|
|
526
|
+
right-click); + at the end shows a hidden column. */
|
|
527
|
+
.fx-th { margin: -4px -6px; padding: 4px 6px; border: 0; border-radius: var(--t-radius); background: none; color: inherit; font: inherit; letter-spacing: inherit; text-transform: inherit; cursor: pointer; }
|
|
528
|
+
.fx-th:hover, .fx-th:focus-visible { background: var(--fx-hover); color: var(--t-strong); outline: none; }
|
|
529
|
+
.fx-th-add { width: 36px; text-align: right; }
|
|
530
|
+
.fx-th--add { font-size: 16px; line-height: 1; }
|
|
531
|
+
.fx-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
|
|
532
|
+
.fx-c-date time { font-variant-numeric: tabular-nums; }
|
|
511
533
|
.fx-stage[data-rank="plain"] { --stage: var(--t-dim); }
|
|
512
|
-
/* The board
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
its bottom edge,
|
|
516
|
-
fx-main--board while the
|
|
517
|
-
|
|
534
|
+
/* The board is one row of lanes (one per group), each about a phone wide.
|
|
535
|
+
Lanes share the width when they fit; more lanes scroll sideways inside
|
|
536
|
+
the board, never as a second row. On a wide, tall screen the board fills
|
|
537
|
+
the page down to its bottom edge, the page itself does not scroll, and
|
|
538
|
+
each lane scrolls inside itself (jobs.js sets fx-main--board while the
|
|
539
|
+
board shows). */
|
|
540
|
+
.fx-board { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(min(85vw, 272px), 1fr); gap: 12px; align-items: start; overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; padding-bottom: 4px; }
|
|
518
541
|
.fx-board__col { display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; padding: 10px 6px 6px 8px; border-radius: var(--t-radius-lg); background: var(--t-s1); }
|
|
519
542
|
.fx-board__head { display: flex; flex: none; align-items: center; gap: 8px; min-width: 0; padding: 2px 4px 4px 2px; }
|
|
520
543
|
.fx-board__sum { flex: none; margin-left: auto; color: var(--t-dim); font-size: 12px; font-variant-numeric: tabular-nums; }
|
|
@@ -522,7 +545,7 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
522
545
|
@media (min-width: 821px) and (min-height: 560px) {
|
|
523
546
|
.fx-main--board { height: 100vh; height: 100dvh; padding: 24px 24px 16px; grid-template-rows: auto auto minmax(0, 1fr); align-content: stretch; }
|
|
524
547
|
.fx-main--board > .fx-jobs { min-height: 0; }
|
|
525
|
-
.fx-main--board .fx-board { height: 100%; overflow-y:
|
|
548
|
+
.fx-main--board .fx-board { height: 100%; overflow-y: hidden; align-items: stretch; }
|
|
526
549
|
.fx-main--board .fx-board__cards { flex: 1; max-height: none; }
|
|
527
550
|
}
|
|
528
551
|
.fx-board__none { padding: 8px 4px; color: var(--t-dim); font-size: 13px; }
|
|
@@ -608,6 +631,70 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
608
631
|
.fx-task__fix h3 { padding: 12px 14px 4px; color: var(--t-warn); }
|
|
609
632
|
.fx-task__fixrow { padding: 9px 14px; border-top: 1px solid var(--fx-line-soft); color: var(--t-strong); font-size: 13.5px; }
|
|
610
633
|
.fx-task__fixrow:first-of-type { border-top: 0; }
|
|
634
|
+
.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; }
|
|
635
|
+
.fx-task__choose > svg { flex: none; width: 20px; height: 20px; color: var(--t-warn); }
|
|
636
|
+
.fx-task__choose > span { flex: 1 1 200px; }
|
|
637
|
+
.fx-task__choices { display: flex; flex-wrap: wrap; gap: 10px; }
|
|
638
|
+
.fx-task__none { display: inline-flex; align-items: center; gap: 6px; color: var(--t-warn); }
|
|
639
|
+
.fx-task__none svg { width: 16px; height: 16px; }
|
|
640
|
+
|
|
641
|
+
/* Reconcile: the queue left, one job and its possible invoices right. */
|
|
642
|
+
.fx-rec { display: grid; grid-template-columns: 280px minmax(0, 1fr); border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper); min-height: 420px; }
|
|
643
|
+
.fx-rec > .fx-empty, .fx-rec > .fx-dim { grid-column: 1 / -1; }
|
|
644
|
+
.fx-rec__queue { display: flex; flex-direction: column; border-right: 1px solid var(--fx-line); max-height: 75vh; overflow-y: auto; }
|
|
645
|
+
.fx-rec__tabs { display: flex; flex-wrap: wrap; gap: 6px; padding: 12px 16px; border-bottom: 1px solid var(--fx-line); }
|
|
646
|
+
.fx-rec__tab { display: inline-flex; align-items: center; min-height: 32px; padding: 0 10px; border: 1px solid var(--fx-line); border-radius: 999px; color: var(--t-text); font-size: 12.5px; font-weight: 600; text-decoration: none; }
|
|
647
|
+
.fx-rec__tab[aria-selected="true"] { border-color: var(--t-primary); background: var(--t-primary); color: var(--t-on-primary); }
|
|
648
|
+
.fx-rec__search { width: 100%; min-width: 0; }
|
|
649
|
+
.fx-rec__results { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
650
|
+
.fx-rec__item { display: grid; gap: 3px; min-height: 44px; padding: 12px 16px; border-bottom: 1px solid var(--fx-line-soft); border-left: 3px solid transparent; color: var(--t-text); text-decoration: none; }
|
|
651
|
+
.fx-rec__item:hover { background: var(--fx-hover); }
|
|
652
|
+
.fx-rec__item[aria-current="true"] { border-left-color: var(--t-accent); background: var(--t-s1); }
|
|
653
|
+
.fx-rec__item-top { display: flex; justify-content: space-between; gap: 8px; }
|
|
654
|
+
.fx-rec__item .fx-num { font-family: var(--t-data); font-variant-numeric: tabular-nums; }
|
|
655
|
+
.fx-rec__detail { display: flex; flex-direction: column; gap: 16px; min-width: 0; padding: 20px 24px; }
|
|
656
|
+
.fx-rec__head { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
|
|
657
|
+
.fx-rec__head > div { display: grid; gap: 2px; }
|
|
658
|
+
.fx-rec__back { display: none; color: var(--t-accent); text-decoration: none; font-size: 13px; }
|
|
659
|
+
.fx-rec__title { margin: 0; font-family: var(--t-display); font-weight: var(--t-display-weight); color: var(--t-strong); font-size: 24px; }
|
|
660
|
+
.fx-rec__table { display: grid; grid-template-columns: 110px minmax(150px, 1fr) repeat(var(--cols), minmax(170px, 1fr)); border: 1px solid var(--fx-line); border-radius: var(--t-radius); overflow-x: auto; }
|
|
661
|
+
.fx-rec__row { display: contents; }
|
|
662
|
+
.fx-rec__row > span { display: flex; flex-direction: column; gap: 2px; padding: 10px 12px; border-top: 1px solid var(--fx-line-soft); font-size: 13.5px; color: var(--t-strong); }
|
|
663
|
+
.fx-rec__row:first-child > span { border-top: 0; background: var(--t-s1); }
|
|
664
|
+
.fx-rec__row > span[role="cell"]:not(.fx-rec__job) { flex-direction: row; align-items: flex-start; gap: 6px; }
|
|
665
|
+
.fx-rec__row > span[role="cell"].fx-rec__acts { flex-direction: column; align-items: stretch; gap: 8px; }
|
|
666
|
+
.fx-rec__acts > .fx-task__link, .fx-rec__card > .fx-task__link { align-self: flex-start; }
|
|
667
|
+
.fx-rec__job { background: color-mix(in srgb, var(--t-accent) 5%, transparent); }
|
|
668
|
+
.fx-rec__row:first-child > .fx-rec__job { background: color-mix(in srgb, var(--t-accent) 10%, var(--t-s1)); }
|
|
669
|
+
.fx-rec__lines { display: flex; flex-direction: column; font-family: var(--t-data); font-variant-numeric: tabular-nums; font-size: 13px; line-height: 1.6; }
|
|
670
|
+
.fx-rec__mark { flex: none; font-weight: 800; }
|
|
671
|
+
[data-mark="same"] > .fx-rec__mark { color: var(--t-success); }
|
|
672
|
+
[data-mark="differs"], [data-mark="differs"] > .fx-rec__mark { color: var(--t-warn); }
|
|
673
|
+
.fx-rec__best { color: var(--t-success); font-size: 12px; font-weight: 700; }
|
|
674
|
+
.fx-rec__foot { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
|
|
675
|
+
.fx-rec__foot-acts { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
676
|
+
.fx-rec__cards { display: none; }
|
|
677
|
+
.fx-rec__card { display: flex; flex-direction: column; gap: 6px; padding: 12px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); font-size: 13.5px; color: var(--t-strong); }
|
|
678
|
+
.fx-rec__card.fx-rec__job { border-color: transparent; }
|
|
679
|
+
.fx-rec__card--best { border-color: var(--t-success); }
|
|
680
|
+
.fx-rec__card-top { display: flex; justify-content: space-between; gap: 8px; }
|
|
681
|
+
.fx-rec__label { color: var(--t-dim); font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
682
|
+
.fx-rec__checks { display: flex; flex-wrap: wrap; gap: 4px 12px; }
|
|
683
|
+
@media (max-width: 1100px) {
|
|
684
|
+
.fx-rec { grid-template-columns: minmax(0, 1fr); min-height: 0; }
|
|
685
|
+
.fx-rec__queue { border-right: 0; max-height: none; }
|
|
686
|
+
.fx-rec--open .fx-rec__queue { display: none; }
|
|
687
|
+
.fx-rec:not(.fx-rec--open) .fx-rec__detail { display: none; }
|
|
688
|
+
.fx-rec__back { display: inline; }
|
|
689
|
+
}
|
|
690
|
+
@media (max-width: 820px) {
|
|
691
|
+
.fx-rec__detail { padding: 16px; }
|
|
692
|
+
.fx-rec__table { display: none; }
|
|
693
|
+
.fx-rec__cards { display: flex; flex-direction: column; gap: 12px; }
|
|
694
|
+
.fx-rec__cards .fx-btn, .fx-rec__foot .fx-btn { min-height: 44px; }
|
|
695
|
+
.fx-rec__foot-acts { width: 100%; }
|
|
696
|
+
.fx-rec__foot-acts .fx-btn { flex: 1 1 auto; }
|
|
697
|
+
}
|
|
611
698
|
.fx-task__custom { margin-top: 24px; }
|
|
612
699
|
.fx-task__custom h3 { margin: 0 0 4px; color: var(--t-dim); font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
|
|
613
700
|
.fx-task__input { width: 100%; min-width: 0; }
|
|
@@ -823,6 +910,30 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
823
910
|
.fx-table__actions { text-align: left; }
|
|
824
911
|
.fx-table__actions .fx-row--end { justify-content: flex-start; }
|
|
825
912
|
.fx-ph__actions, .fx-ph__actions .fx-btn { width: 100%; }
|
|
913
|
+
/* People: the name with its actions (⋯, Approve) on one line, then the
|
|
914
|
+
role select full width, then the status pill when there is one. */
|
|
915
|
+
.fx-people tr { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 8px 10px; }
|
|
916
|
+
.fx-people td, .fx-people td:nth-child(2), .fx-people td:nth-child(3) { display: block; width: auto; margin: 0; padding: 0; }
|
|
917
|
+
.fx-people td:nth-child(1) { grid-area: 1 / 1; }
|
|
918
|
+
.fx-people td:nth-child(4) { grid-area: 1 / 2; }
|
|
919
|
+
.fx-people td:nth-child(2) { grid-area: 2 / 1 / 3 / -1; }
|
|
920
|
+
.fx-people td:nth-child(2) .fx-select { width: 100%; }
|
|
921
|
+
.fx-people td:nth-child(3) { grid-area: 3 / 1 / 4 / -1; }
|
|
922
|
+
.fx-people td:nth-child(3):empty { display: none; }
|
|
923
|
+
/* Jobs list: the tick at the left of each row and group head; the client
|
|
924
|
+
and total on the first line, then each other column on its own line,
|
|
925
|
+
with its name. */
|
|
926
|
+
.fx-table--jobs tr { display: grid; grid-template-columns: 28px minmax(0, 1fr) auto; align-items: center; gap: 4px 8px; }
|
|
927
|
+
.fx-table--jobs td, .fx-table--jobs td:nth-child(2), .fx-table--jobs td:nth-child(3) { display: block; width: auto; margin: 0; padding: 0; }
|
|
928
|
+
.fx-table--jobs tr > td.fx-tick-cell { grid-column: 1; grid-row: 1; align-self: start; padding-top: 2px; }
|
|
929
|
+
.fx-table--jobs tr.fx-row-link > td:not(.fx-tick-cell) { grid-column: 2 / -1; }
|
|
930
|
+
.fx-table--jobs tr.fx-row-link > td.fx-c-client { grid-area: 1 / 2; }
|
|
931
|
+
.fx-table--jobs tr.fx-row-link > td.fx-c-total { grid-area: 1 / 3; }
|
|
932
|
+
.fx-table--jobs td.fx-c-end { display: none; }
|
|
933
|
+
.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; }
|
|
934
|
+
.fx-table--jobs tr.fx-table__group { grid-template-columns: 28px minmax(0, 1fr); background: var(--t-s1); }
|
|
935
|
+
.fx-table--jobs tr.fx-table__group > td.fx-tick-cell { grid-row: 1; align-self: center; padding: 0; }
|
|
936
|
+
.fx-table--jobs tr.fx-table__group > td { background: none; }
|
|
826
937
|
}
|
|
827
938
|
|
|
828
939
|
@keyframes fx-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
@@ -921,6 +1032,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
921
1032
|
.fx-cfm > .fx-notice { margin: 12px 16px 0; }
|
|
922
1033
|
.fx-cfm__foot { display: flex; justify-content: space-between; gap: 12px; padding: 12px 16px; border-top: 1px solid var(--fx-line-soft); color: var(--t-dim); font-size: 13px; }
|
|
923
1034
|
.fx-pop__item:disabled { opacity: .5; cursor: default; }
|
|
1035
|
+
.fx-pop__item[data-tone="danger"], .fx-pop__item[data-tone="danger"] .fx-pop__tick { color: var(--t-danger); }
|
|
924
1036
|
.fx-pop__item .fx-lucide { width: 15px; height: 15px; }
|
|
925
1037
|
@media (max-width: 720px) {
|
|
926
1038
|
.fx-cfm { grid-template-columns: minmax(0, 1fr) 40px 40px; }
|
|
@@ -1019,3 +1131,105 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1019
1131
|
.fx-pick__row[hidden] { display: none; }
|
|
1020
1132
|
.fx-pick__main { display: grid; gap: 2px; min-width: 0; }
|
|
1021
1133
|
.fx-pick > .fx-dim { padding: 14px; }
|
|
1134
|
+
|
|
1135
|
+
/* The Invoices list head (invoicing.js): tabs, search and the send bar stay
|
|
1136
|
+
at the top while rows scroll up under them; a shadow shows once they do.
|
|
1137
|
+
Narrow, the app bar (60px) sits above it. */
|
|
1138
|
+
.fx-listhead { position: sticky; top: 0; z-index: 5; margin: -12px calc(-1 * clamp(16px, 4vw, 48px)) 0; padding: 12px clamp(16px, 4vw, 48px) 0; background: var(--t-bg); transition: box-shadow .15s; }
|
|
1139
|
+
.fx-listhead[data-stuck] { box-shadow: 0 10px 12px -12px rgba(0, 0, 0, .45); }
|
|
1140
|
+
.fx-listhead .fx-sendbar { margin-bottom: 12px; }
|
|
1141
|
+
.fx-listhead__edge { height: 0; margin-bottom: -20px; }
|
|
1142
|
+
@media (max-width: 820px) { .fx-listhead { top: 60px; } }
|
|
1143
|
+
/* On a phone the head would hide too many rows: it scrolls away. */
|
|
1144
|
+
@media (max-width: 560px) { .fx-listhead { position: static; } }
|
|
1145
|
+
|
|
1146
|
+
/* The bulk action bar (bulk-bar.js): floats at the foot while rows are
|
|
1147
|
+
ticked. "N selected ×", then icon actions in groups, then More. It takes
|
|
1148
|
+
the theme's own surface, line, corners and primary button, like a menu. */
|
|
1149
|
+
.fx-bulk {
|
|
1150
|
+
position: sticky; bottom: 16px; z-index: 6; justify-self: center; display: flex; align-items: center; gap: 2px; max-width: 100%;
|
|
1151
|
+
padding: 5px; border: 1px solid var(--fx-line); border-radius: var(--t-radius-lg); background: var(--t-paper); color: var(--t-text);
|
|
1152
|
+
box-shadow: var(--t-shadow), 0 12px 32px rgba(0, 0, 0, .16);
|
|
1153
|
+
font: 500 13.5px/1 var(--t-body); white-space: nowrap;
|
|
1154
|
+
}
|
|
1155
|
+
.fx-bulk[hidden] { display: none; }
|
|
1156
|
+
.fx-bulk[aria-busy="true"] { opacity: .7; pointer-events: none; }
|
|
1157
|
+
.fx-bulk__acts { display: flex; align-items: center; gap: 2px; min-width: 0; }
|
|
1158
|
+
.fx-bulk__count { display: inline-flex; align-items: center; gap: 6px; flex: none; margin-right: 6px; padding: 3px 4px 3px 10px; border: 1px solid var(--t-accent-brd, var(--fx-line)); border-radius: var(--t-radius); background: var(--t-accent-bg); color: var(--t-accent); font-weight: 600; }
|
|
1159
|
+
.fx-bulk__clear, .fx-bulk__act { position: relative; display: inline-flex; align-items: center; gap: 6px; padding: 7px 9px; border: 1px solid transparent; border-radius: var(--t-radius); background: none; color: inherit; font: inherit; cursor: pointer; }
|
|
1160
|
+
.fx-bulk__clear { padding: 3px; color: inherit; }
|
|
1161
|
+
.fx-bulk__act:hover, .fx-bulk__act:focus-visible, .fx-bulk__act[aria-expanded="true"] { background: var(--fx-hover); color: var(--t-strong); outline: none; }
|
|
1162
|
+
.fx-bulk__clear:hover, .fx-bulk__clear:focus-visible { background: color-mix(in srgb, var(--t-accent) 14%, transparent); outline: none; }
|
|
1163
|
+
.fx-bulk__act--primary, .fx-bulk__act--primary:hover, .fx-bulk__act--primary:focus-visible { background: var(--t-primary); border-color: var(--t-primary); color: var(--t-on-primary); font-weight: 600; }
|
|
1164
|
+
.fx-bulk__act--primary:hover { background: color-mix(in srgb, var(--t-primary) 86%, var(--t-text)); }
|
|
1165
|
+
.fx-bulk__act--icon { padding: 7px; }
|
|
1166
|
+
.fx-bulk__act[data-tone="danger"] { color: var(--t-danger); }
|
|
1167
|
+
.fx-bulk__act[data-tone="danger"]:hover { background: var(--t-danger-bg); }
|
|
1168
|
+
.fx-bulk__sep { flex: none; width: 1px; height: 18px; margin: 0 4px; background: var(--fx-line); }
|
|
1169
|
+
/* An icon-only action says its name on hover, in a tip above it. */
|
|
1170
|
+
.fx-bulk [data-tip]:hover::after, .fx-bulk [data-tip]:focus-visible::after {
|
|
1171
|
+
content: attr(data-tip); position: absolute; bottom: calc(100% + 10px); left: 50%; transform: translateX(-50%);
|
|
1172
|
+
padding: 4px 8px; border: 1px solid var(--fx-line); border-radius: var(--t-radius); background: var(--t-paper); color: var(--t-strong);
|
|
1173
|
+
font: 500 12px/1.2 var(--t-body); pointer-events: none; box-shadow: var(--t-shadow), 0 4px 12px rgba(0, 0, 0, .12);
|
|
1174
|
+
}
|
|
1175
|
+
/* More: a compact list, one row per action, like a dropdown. */
|
|
1176
|
+
.fx-pop.fx-bulk__more { width: 248px; min-width: 0; padding: 4px; }
|
|
1177
|
+
.fx-bulk__search { width: 100%; height: 30px; margin-bottom: 2px; padding: 4px 8px; font-size: 13px; }
|
|
1178
|
+
.fx-bulk__more .fx-pop__head { padding: 6px 8px 2px; font-size: 10.5px; }
|
|
1179
|
+
.fx-bulk__more .fx-pop__item { gap: 8px; padding: 4px 8px; font-size: 13px; line-height: 1.25; }
|
|
1180
|
+
.fx-bulk__more .fx-pop__item .fx-lucide { width: 14px; height: 14px; }
|
|
1181
|
+
.fx-bulk__grid { display: grid; }
|
|
1182
|
+
.fx-bulk__grid .fx-pop__item[data-tone="danger"] { color: var(--t-danger); }
|
|
1183
|
+
.fx-bulk__none { margin: 8px 10px; color: var(--t-dim); font-size: 13.5px; }
|
|
1184
|
+
@media (max-width: 560px) {
|
|
1185
|
+
.fx-bulk { bottom: 10px; padding: 4px; }
|
|
1186
|
+
.fx-bulk__count { margin-right: 2px; padding-left: 8px; }
|
|
1187
|
+
.fx-bulk__act { padding: 7px 6px; }
|
|
1188
|
+
.fx-bulk__sep { margin: 0 2px; }
|
|
1189
|
+
.fx-bulk__act:not(.fx-bulk__act--primary) .fx-bulk__word { display: none; }
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* Xero › Import (server/ui/xero-import.js): Review compares one fact per row
|
|
1193
|
+
with ✓ or ≠ between the sides; the list and history tick rows for the bulk
|
|
1194
|
+
bar. */
|
|
1195
|
+
.fx-xi { display: flex; flex-direction: column; gap: 16px; min-width: 0; max-width: 980px; }
|
|
1196
|
+
.fx-xi .fx-lucide { width: 16px; height: 16px; }
|
|
1197
|
+
.fx-xi__views { display: inline-flex; border: 1px solid var(--fx-line); }
|
|
1198
|
+
.fx-xi__view { display: inline-grid; place-items: center; width: 40px; height: 36px; border: 0; background: transparent; color: var(--t-dim); cursor: pointer; }
|
|
1199
|
+
.fx-xi__view[aria-selected="true"] { background: var(--t-primary); color: var(--t-on-primary); }
|
|
1200
|
+
.fx-xi__count { display: inline-flex; align-items: center; gap: 10px; font-variant-numeric: tabular-nums; }
|
|
1201
|
+
.fx-xi__progress { display: inline-block; width: 120px; height: 4px; background: var(--fx-line); }
|
|
1202
|
+
.fx-xi__progress > span { display: block; height: 100%; background: var(--t-primary); }
|
|
1203
|
+
.fx-xi__bulk { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 10px 14px; border: 1px dashed var(--t-primary); background: color-mix(in srgb, var(--t-primary) 6%, transparent); color: var(--t-strong); font-size: 13.5px; }
|
|
1204
|
+
.fx-xi__bulk-text { margin-right: auto; }
|
|
1205
|
+
.fx-xi__bulk .fx-btn { display: inline-flex; align-items: center; gap: 6px; }
|
|
1206
|
+
.fx-xi__bulk .fx-xi__search { flex: 1; min-width: 0; margin-right: 0; }
|
|
1207
|
+
.fx-xi-cmp { width: 100%; border-collapse: collapse; border: 1px solid var(--fx-line); background: var(--t-s1); }
|
|
1208
|
+
.fx-xi-cmp th, .fx-xi-cmp td { padding: 11px 16px; border-top: 1px solid var(--fx-line-soft); text-align: left; font-size: 14px; color: var(--t-strong); }
|
|
1209
|
+
.fx-xi-cmp thead th { border-top: 0; padding: 14px 16px; }
|
|
1210
|
+
.fx-xi-cmp tbody th { width: 44px; color: var(--t-dim); }
|
|
1211
|
+
.fx-xi-cmp td:nth-child(2), .fx-xi-cmp th:nth-child(2) { width: 42%; }
|
|
1212
|
+
.fx-xi-cmp__mid { width: 48px; text-align: center !important; }
|
|
1213
|
+
.fx-xi__mark { height: 22px; width: auto; vertical-align: middle; }
|
|
1214
|
+
.fx-xi-cmp thead th:last-child { display: flex; align-items: center; gap: 10px; color: var(--t-dim); }
|
|
1215
|
+
.fx-xi__agree { display: inline-grid; place-items: center; width: 24px; height: 24px; }
|
|
1216
|
+
.fx-xi__agree[data-state="same"] { color: var(--t-success); }
|
|
1217
|
+
.fx-xi__agree[data-state="differs"] { color: var(--t-warn); }
|
|
1218
|
+
.fx-xi__acts { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
|
|
1219
|
+
.fx-xi__acts .fx-btn { display: inline-flex; align-items: center; gap: 6px; }
|
|
1220
|
+
.fx-xi__done { display: flex; align-items: center; gap: 12px; padding: 24px; border: 1px solid var(--fx-line); color: var(--t-success); }
|
|
1221
|
+
.fx-xi__done p { margin: 0; color: var(--t-strong); }
|
|
1222
|
+
.fx-xi__scroll { overflow-x: auto; }
|
|
1223
|
+
.fx-xi-list .fx-xi__mark { height: 18px; }
|
|
1224
|
+
.fx-xi-list__mid { width: 56px; text-align: center; color: var(--t-dim); white-space: nowrap; }
|
|
1225
|
+
.fx-xi-list__mid .fx-dim { margin-left: 4px; font-size: 12px; }
|
|
1226
|
+
.fx-xi-list tr[data-state="linked"] .fx-xi-list__mid { color: var(--t-success); }
|
|
1227
|
+
.fx-xi-list tr[data-state="invoice_fits"] .fx-xi-list__mid a, .fx-xi-list tr[data-state="job_fits"] .fx-xi-list__mid a { color: var(--t-warn); }
|
|
1228
|
+
@media (max-width: 820px) {
|
|
1229
|
+
.fx-xi-cmp th, .fx-xi-cmp td { padding: 10px 8px; font-size: 13px; }
|
|
1230
|
+
.fx-xi-cmp tbody th { width: 28px; }
|
|
1231
|
+
.fx-xi-cmp__mid { width: 28px; }
|
|
1232
|
+
.fx-xi__acts .fx-btn { flex: 1 1 auto; min-height: 44px; justify-content: center; }
|
|
1233
|
+
}
|
|
1234
|
+
.fx-xi-count { font-size: 18px; }
|
|
1235
|
+
.fx-xi-list td.fx-num:not(.fx-strong) { text-align: left; }
|