@arcforgelabs/flow 2026.10.46 → 2026.10.48-1
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/VISION.md +10 -10
- package/npm-shrinkwrap.json +11 -11
- package/package.json +1 -1
- package/server/api/invoicing.js +89 -22
- package/server/auth/people.js +3 -1
- package/server/auth/routes.js +4 -1
- package/server/core/contacts.js +160 -0
- package/server/core/directory.js +4 -9
- package/server/core/invoice-rules.js +34 -4
- package/server/core/invoices.js +93 -1
- package/server/core/invoicing-rules.js +20 -3
- package/server/core/job-board.js +1 -1
- package/server/core/sending.js +141 -25
- package/server/db/doctor.js +1 -1
- package/server/db/index.js +19 -0
- package/server/db/migrations/0021_flow_schema.sql +68 -0
- package/server/db/migrations/0022_contacts.sql +11 -0
- package/server/db/migrations/meta/0021_snapshot.json +4377 -0
- package/server/db/migrations/meta/0022_snapshot.json +4446 -0
- package/server/db/migrations/meta/_journal.json +14 -0
- package/server/db/schema.js +15 -0
- package/server/integrations/port.js +2 -0
- package/server/integrations/schema.js +3 -2
- package/server/integrations/seazona/sweep.js +11 -8
- package/server/integrations/sentry/index.js +14 -7
- package/server/integrations/setup.js +1 -0
- package/server/integrations/xero/catalogue.js +82 -14
- package/server/integrations/xero/client.js +37 -0
- package/server/integrations/xero/create-contact.js +126 -0
- package/server/integrations/xero/delete-invoice.js +104 -0
- package/server/integrations/xero/drafts.js +19 -8
- package/server/integrations/xero/index.js +9 -0
- package/server/integrations/xero/migrations/0011_xero_invoice_deleted.sql +11 -0
- package/server/integrations/xero/migrations/0012_xero_contact_details.sql +16 -0
- package/server/integrations/xero/migrations/meta/_journal.json +14 -0
- package/server/integrations/xero/routes.js +21 -0
- package/server/integrations/xero/settings.js +3 -0
- package/server/ui/alerts.js +2 -1
- package/server/ui/app.js +113 -28
- package/server/ui/bulk-bar.js +18 -5
- package/server/ui/columns.js +5 -2
- package/server/ui/contacts.js +260 -0
- package/server/ui/custom-fields.js +22 -7
- package/server/ui/flow.css +275 -24
- package/server/ui/invoicing.js +374 -70
- package/server/ui/job-board.js +10 -0
- package/server/ui/job-change.js +4 -3
- package/server/ui/job-view.js +3 -1
- package/server/ui/jobs.js +150 -19
- package/server/ui/kit.js +220 -45
- package/server/ui/list-views.js +50 -10
- package/server/ui/mobile.js +729 -0
- package/server/ui/monitoring.js +51 -0
- package/server/ui/nav-pins.js +44 -0
- package/server/ui/pager.js +57 -10
- package/server/ui/price-list.js +1 -1
- package/server/ui/price-reconcile.js +12 -12
- package/server/ui/reconcile.js +18 -17
- package/server/ui/routes.js +1 -1
- package/server/ui/saved-views.js +2 -1
- package/server/ui/sidecar.js +106 -6
- package/server/ui/view-bar.js +22 -11
- package/server/ui/xero-import.js +48 -19
package/server/ui/flow.css
CHANGED
|
@@ -282,6 +282,14 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
282
282
|
box-shadow: 0 8px 24px rgba(0, 0, 0, .18); animation: fx-rise 160ms var(--fx-ease);
|
|
283
283
|
}
|
|
284
284
|
.fx-toast[data-tone="danger"] { background: var(--t-danger); color: var(--t-paper); }
|
|
285
|
+
.fx-toast--stay { display: flex; align-items: flex-start; gap: 12px; }
|
|
286
|
+
.fx-toast__text { flex: 1; min-width: 0; }
|
|
287
|
+
.fx-toast__close {
|
|
288
|
+
flex: none; display: grid; place-items: center; width: 28px; height: 28px; margin: -6px -8px -6px 0;
|
|
289
|
+
border: 0; border-radius: var(--t-radius); background: transparent; color: inherit; cursor: pointer; opacity: .85;
|
|
290
|
+
}
|
|
291
|
+
.fx-toast__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 14%, transparent); }
|
|
292
|
+
.fx-toast__close svg { width: 16px; height: 16px; }
|
|
285
293
|
|
|
286
294
|
/* ── Table and people ───────────────────────────────────────────────────── */
|
|
287
295
|
.fx-table { width: 100%; border-collapse: collapse; font-size: 14px; }
|
|
@@ -357,6 +365,7 @@ a.fx-link:hover, a.fx-link:focus-visible { border-color: var(--t-strong); }
|
|
|
357
365
|
.fx-tile .fx-logo--icon { background: var(--fx-hover); color: var(--t-strong); box-shadow: inset 0 0 0 1px var(--fx-line); }
|
|
358
366
|
.fx-tile h3 { overflow: hidden; color: var(--t-strong); font: 600 14.5px/1.35 var(--t-body); text-overflow: ellipsis; white-space: nowrap; }
|
|
359
367
|
.fx-tile__meta { color: var(--t-dim); font-size: 12.5px; white-space: nowrap; }
|
|
368
|
+
.fx-tile__go { display: none; }
|
|
360
369
|
.fx-logo--icon .fx-lucide { width: 22px; height: 22px; }
|
|
361
370
|
.fx-logo img { width: 28px; height: 28px; object-fit: contain; }
|
|
362
371
|
.fx-logo--lg { width: 52px; height: 52px; }
|
|
@@ -585,6 +594,30 @@ a.fx-strong:hover { text-decoration: underline; }
|
|
|
585
594
|
.fx-pop__item[aria-checked="true"] { color: var(--t-strong); font-weight: 600; }
|
|
586
595
|
.fx-pop__tick { display: inline-grid; place-items: center; flex: none; width: 16px; height: 16px; color: var(--t-accent); }
|
|
587
596
|
.fx-pop__sep { height: 1px; margin: 6px 4px; background: var(--fx-line-soft); }
|
|
597
|
+
/* A row with a trailing control (the sidebar pin). The control does not
|
|
598
|
+
close the menu. An unpinned pin shows when the pointer is on the row. */
|
|
599
|
+
.fx-pop__row { display: flex; align-items: center; gap: 2px; }
|
|
600
|
+
.fx-pop__row > .fx-pop__item { flex: 1; min-width: 0; }
|
|
601
|
+
.fx-pop__row > .fx-pop__item > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
602
|
+
.fx-pop__pin, .fx-pop__pin--fixed {
|
|
603
|
+
display: inline-grid; place-items: center; flex: none; width: 28px; height: 28px;
|
|
604
|
+
border: 0; border-radius: var(--t-radius); background: none; color: var(--t-dim);
|
|
605
|
+
}
|
|
606
|
+
.fx-pop__pin { cursor: pointer; }
|
|
607
|
+
.fx-pop__pin .fx-lucide, .fx-pop__pin--fixed .fx-lucide, .fx-pop__pin .fx-ico, .fx-pop__pin--fixed .fx-ico { width: 15px; height: 15px; }
|
|
608
|
+
.fx-pop__pin[aria-pressed="false"] { opacity: 0; }
|
|
609
|
+
.fx-pop__row:hover .fx-pop__pin[aria-pressed="false"],
|
|
610
|
+
.fx-pop__row:focus-within .fx-pop__pin[aria-pressed="false"],
|
|
611
|
+
.fx-pop__pin:focus-visible { opacity: 1; }
|
|
612
|
+
.fx-pop__pin[aria-pressed="true"], .fx-pop__pin--fixed { color: var(--t-strong); opacity: 1; }
|
|
613
|
+
.fx-pop__pin:hover, .fx-pop__pin:focus-visible { background: var(--fx-hover); color: var(--t-strong); outline: none; }
|
|
614
|
+
.fx-pop.fx-pop--side { min-width: 240px; }
|
|
615
|
+
.fx-pop--side .fx-pop__tick { color: inherit; }
|
|
616
|
+
@media (hover: none) { .fx-pop__pin[aria-pressed="false"] { opacity: 1; } }
|
|
617
|
+
@media (max-width: 820px) {
|
|
618
|
+
.fx-pop--side .fx-pop__item { min-height: 44px; }
|
|
619
|
+
.fx-pop--side .fx-pop__pin, .fx-pop--side .fx-pop__pin--fixed { width: 44px; height: 44px; }
|
|
620
|
+
}
|
|
588
621
|
.fx-pop__foot { display: flex; justify-content: flex-end; padding: 6px 4px 2px; border-top: 1px solid var(--fx-line-soft); margin-top: 6px; }
|
|
589
622
|
/* A chip with an × that clears it (as ClickUp's "Clear all filters"). */
|
|
590
623
|
.fx-chipset { display: inline-flex; align-items: stretch; }
|
|
@@ -798,6 +831,29 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
|
|
|
798
831
|
.fx-main--board .fx-board { height: 100%; overflow-y: hidden; align-items: stretch; }
|
|
799
832
|
.fx-main--board .fx-board__cards { flex: 1; max-height: none; }
|
|
800
833
|
}
|
|
834
|
+
/* Phone: one lane fills the width and snaps. Stage tabs (jobs.js) name the
|
|
835
|
+
lane; the lane's own head is hidden. Drag stays on a wider screen. */
|
|
836
|
+
.fx-board-tabs { display: none; }
|
|
837
|
+
@media (max-width: 639px) {
|
|
838
|
+
.fx-main--board .fx-board-tabs:not([hidden]) {
|
|
839
|
+
display: flex; flex: 1 1 100%; order: -1; gap: 2px; min-width: 0; max-width: 100%;
|
|
840
|
+
overflow-x: auto; scrollbar-width: none;
|
|
841
|
+
}
|
|
842
|
+
.fx-board-tabs button {
|
|
843
|
+
flex: none; display: inline-flex; align-items: center; gap: 8px; min-width: 44px; min-height: 44px;
|
|
844
|
+
padding: 0 12px; border: 0; border-bottom: 2px solid transparent; background: transparent;
|
|
845
|
+
color: var(--t-dim); font: 600 14px var(--t-body); cursor: pointer;
|
|
846
|
+
}
|
|
847
|
+
.fx-board-tabs button[aria-selected="true"] { color: var(--t-strong); border-bottom-color: var(--t-brand); }
|
|
848
|
+
.fx-main--board .fx-frame__body { padding: 0; }
|
|
849
|
+
.fx-main--board .fx-board {
|
|
850
|
+
--fx-lane: 100%; grid-auto-columns: 100%; gap: 0; min-width: 0; width: 100%;
|
|
851
|
+
scroll-snap-type: x mandatory; scrollbar-width: none;
|
|
852
|
+
}
|
|
853
|
+
.fx-main--board .fx-board__col { scroll-snap-align: start; scroll-snap-stop: always; border-radius: 0; }
|
|
854
|
+
.fx-main--board .fx-board__head { display: none; }
|
|
855
|
+
.fx-main--board .fx-board__cards { max-height: none; padding: 8px; }
|
|
856
|
+
}
|
|
801
857
|
.fx-board__none { padding: 8px 4px; color: var(--t-dim); font-size: 13px; }
|
|
802
858
|
.fx-table--jobs tbody tr { cursor: pointer; }
|
|
803
859
|
|
|
@@ -972,13 +1028,74 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
|
|
|
972
1028
|
.fx-task__activity li::after { content: ""; position: absolute; left: 5.5px; top: 16px; bottom: 0; width: 1px; background: var(--fx-line); }
|
|
973
1029
|
.fx-task__activity li:last-child::after { display: none; }
|
|
974
1030
|
.fx-task__activity time { display: block; margin-top: 1px; color: var(--t-mute); font-size: 11.5px; }
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1031
|
+
/* A record's phone bar and foot. Hidden above a phone, so the desk panel and
|
|
1032
|
+
the invoice header stay as they are. */
|
|
1033
|
+
.fx-record, .fx-record__foot { display: none; }
|
|
1034
|
+
@media (max-width: 639px) {
|
|
1035
|
+
/* The job panel is the whole screen. Back, the client and the next job
|
|
1036
|
+
sit in the bar; Activity and Fix are tabs under it; the action is fixed
|
|
1037
|
+
at the foot, in thumb reach. */
|
|
1038
|
+
.fx-dialog.fx-task {
|
|
1039
|
+
display: flex; flex-direction: column; inset: 0; width: 100%; max-width: none;
|
|
1040
|
+
height: 100%; max-height: none; margin: 0; border-radius: 0; overflow: hidden;
|
|
1041
|
+
}
|
|
1042
|
+
.fx-dialog.fx-task > .fx-record { flex: none; }
|
|
1043
|
+
.fx-dialog.fx-task .fx-task__grid {
|
|
1044
|
+
display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto;
|
|
1045
|
+
grid-template-columns: minmax(0, 1fr);
|
|
1046
|
+
}
|
|
1047
|
+
.fx-dialog.fx-task .fx-task__main { padding: 16px; }
|
|
1048
|
+
.fx-dialog.fx-task .fx-task__fields { grid-template-columns: minmax(0, 1fr); }
|
|
1049
|
+
.fx-dialog.fx-task .fx-task__head, .fx-dialog.fx-task .fx-task__title { display: none; }
|
|
1050
|
+
.fx-dialog.fx-task .fx-task__grid:has(> .fx-sidecar [data-tab="fix"][aria-selected="true"]) { grid-template-columns: minmax(0, 1fr); }
|
|
1051
|
+
.fx-task__grid > .fx-sidecar__head {
|
|
1052
|
+
position: sticky; top: 0; z-index: 2; flex: none;
|
|
1053
|
+
background: var(--t-paper); border-bottom: 1px solid var(--fx-line-soft);
|
|
1054
|
+
}
|
|
1055
|
+
.fx-task__grid > .fx-sidecar__head .fx-sidecar__close, .fx-sidecar--page .fx-sidecar__close { display: none; }
|
|
1056
|
+
.fx-task__grid > .fx-sidecar__head .fx-sidecar__tab, .fx-sidecar--page .fx-sidecar__tab { min-width: 44px; min-height: 44px; }
|
|
1057
|
+
.fx-sidecar--page { border-left: 0; border-top: 1px solid var(--fx-line); }
|
|
1058
|
+
.fx-sidecar--page .fx-sidecar__body { scroll-margin-top: 52px; }
|
|
1059
|
+
.fx-dialog.fx-task .fx-task__actions:not(:empty) {
|
|
1060
|
+
position: fixed; left: 0; right: 0; bottom: 0; z-index: 5; margin: 0;
|
|
1061
|
+
padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
|
|
1062
|
+
background: var(--t-paper); border-top: 1px solid var(--fx-line);
|
|
1063
|
+
}
|
|
1064
|
+
.fx-dialog.fx-task .fx-task__actions .fx-btn { flex: 1; min-height: 44px; }
|
|
1065
|
+
.fx-dialog.fx-task:has(.fx-task__actions:not(:empty)) .fx-task__grid,
|
|
1066
|
+
.fx-dialog.fx-task:has([data-action="accept"]) .fx-task__grid { padding-bottom: 68px; }
|
|
1067
|
+
.fx-dialog.fx-task:has([data-action="accept"]) .fx-task__actions { display: none; }
|
|
1068
|
+
.fx-invoice [data-action="accept"], .fx-dialog.fx-task [data-action="accept"] {
|
|
1069
|
+
position: fixed; left: 8px; right: 8px; bottom: 8px; z-index: 6; width: auto; min-height: 44px;
|
|
1070
|
+
}
|
|
1071
|
+
.fx-record {
|
|
1072
|
+
display: flex; align-items: center; gap: 2px; min-height: 48px; padding: 2px 4px;
|
|
1073
|
+
background: var(--t-paper); border-bottom: 1px solid var(--fx-line-soft);
|
|
1074
|
+
}
|
|
1075
|
+
.fx-record__back, .fx-record__step, .fx-record__menu {
|
|
1076
|
+
flex: none; display: inline-grid; place-items: center; width: 44px; height: 44px;
|
|
1077
|
+
min-width: 44px; min-height: 44px; padding: 0; border: 0; border-radius: var(--t-radius);
|
|
1078
|
+
background: transparent; color: var(--t-strong); cursor: pointer;
|
|
1079
|
+
}
|
|
1080
|
+
.fx-record__back:disabled, .fx-record__step:disabled { opacity: 0.35; cursor: default; }
|
|
1081
|
+
.fx-record__back:focus-visible, .fx-record__step:focus-visible, .fx-record__menu:focus-visible { box-shadow: var(--fx-focus); }
|
|
1082
|
+
.fx-record__menu[hidden] { display: none; }
|
|
1083
|
+
.fx-record__title, .fx-record .fx-title {
|
|
1084
|
+
flex: 1; min-width: 0; margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
1085
|
+
color: var(--t-strong); font: 600 16px/1.2 var(--t-body); letter-spacing: 0;
|
|
1086
|
+
}
|
|
1087
|
+
.fx-record .fx-ph { flex: 1; min-width: 0; min-height: 0; gap: 8px; background: transparent; }
|
|
1088
|
+
.fx-record .fx-ph__text { min-width: 0; }
|
|
1089
|
+
.fx-record__end { display: flex; flex: none; align-items: center; }
|
|
1090
|
+
.fx-record__foot:not([hidden]) {
|
|
1091
|
+
display: flex; position: fixed; left: 0; right: 0; bottom: 0; z-index: 5; gap: 8px;
|
|
1092
|
+
padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
|
|
1093
|
+
background: var(--t-paper); border-top: 1px solid var(--fx-line);
|
|
1094
|
+
}
|
|
1095
|
+
.fx-record__foot .fx-btn { flex: 1; min-height: 44px; }
|
|
1096
|
+
.fx-invoice > .fx-pager, .fx-invoice .fx-ph__actions { display: none; }
|
|
1097
|
+
.fx-invoice:has(.fx-record__foot:not([hidden])), .fx-invoice:has([data-action="accept"]) { padding-bottom: 72px; }
|
|
1098
|
+
.fx-invoice:has([data-action="accept"]) .fx-record__foot { display: none; }
|
|
982
1099
|
}
|
|
983
1100
|
|
|
984
1101
|
/* ── Dialog ─────────────────────────────────────────────────────────────── */
|
|
@@ -992,6 +1109,17 @@ th:hover > .fx-th-grip::after { background: var(--t-brd-hi); }
|
|
|
992
1109
|
.fx-dialog__form { display: grid; gap: 16px; padding: 22px; }
|
|
993
1110
|
.fx-dialog__title { color: var(--t-strong); font: var(--t-display-weight) 21px/1.2 var(--t-display); }
|
|
994
1111
|
.fx-dialog__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }
|
|
1112
|
+
.fx-dialog[data-phone-layout="sheet"] {
|
|
1113
|
+
position: fixed; inset: auto 0 0 0; width: 100%; max-width: 100%; max-height: min(80vh, 100%);
|
|
1114
|
+
margin: 0; border-radius: var(--t-radius-lg) var(--t-radius-lg) 0 0;
|
|
1115
|
+
}
|
|
1116
|
+
.fx-sheet__choices { display: grid; gap: 4px; }
|
|
1117
|
+
.fx-sheet__choice {
|
|
1118
|
+
display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
|
|
1119
|
+
min-height: 44px; padding: 0 12px; border: 0; border-radius: var(--t-radius);
|
|
1120
|
+
background: transparent; color: var(--t-strong); font: 600 15px var(--t-body); text-align: left; cursor: pointer;
|
|
1121
|
+
}
|
|
1122
|
+
.fx-sheet__choice:hover { background: var(--fx-hover); }
|
|
995
1123
|
|
|
996
1124
|
/* Search (search.js): a box near the top, results grouped under it. */
|
|
997
1125
|
.fx-search-dialog { width: min(600px, calc(100vw - 32px)); margin-top: 12vh; }
|
|
@@ -1152,6 +1280,7 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1152
1280
|
background: rgba(0, 0, 0, .32);
|
|
1153
1281
|
}
|
|
1154
1282
|
.fx-nav__report { margin-top: 8px; }
|
|
1283
|
+
.fx-nav a, .fx-nav__report, .fx-nav__more { min-height: 44px; }
|
|
1155
1284
|
.fx-side__end { margin-left: auto; padding: 0; border-top: 0; }
|
|
1156
1285
|
#user-button { flex: none; }
|
|
1157
1286
|
}
|
|
@@ -1174,13 +1303,34 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1174
1303
|
/* ── Phones ─────────────────────────────────────────────────────────────── */
|
|
1175
1304
|
@media (max-width: 639px) {
|
|
1176
1305
|
.fx-main { padding-top: 20px; }
|
|
1306
|
+
/* A two-line list needs the rows, not the gaps, so seven fit at 375×667.
|
|
1307
|
+
The alert stays one line. The toolbar stays one scrolling row. */
|
|
1308
|
+
.fx-main--frame:has([data-phone-layout="two-line"]) { gap: 8px; padding-top: 12px; }
|
|
1309
|
+
.fx-alerts { flex-wrap: nowrap; }
|
|
1310
|
+
.fx-alerts__text { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1311
|
+
/* Tabs stay on their own row. The tools beside them stay one row and
|
|
1312
|
+
scroll, so a chip is never covered by the next control. */
|
|
1313
|
+
.fx-listhead, .fx-listhead > .fx-toolbar { min-width: 0; max-width: 100%; }
|
|
1314
|
+
.fx-listhead .fx-toolbar__tools { flex: 1 1 auto; min-width: 0; overflow-x: auto; scrollbar-width: none; }
|
|
1315
|
+
.fx-listhead .fx-toolbar__end,
|
|
1316
|
+
.fx-listhead .fx-toolbar__end:has(> .fx-toolbar__tools) {
|
|
1317
|
+
flex: 1 1 100%; flex-wrap: nowrap; min-width: 0; overflow-x: auto; scrollbar-width: none; row-gap: 0;
|
|
1318
|
+
}
|
|
1177
1319
|
.fx-title { font-size: 22px; }
|
|
1178
1320
|
.fx-table thead { display: none; }
|
|
1179
1321
|
.fx-table.fx-table--sized { width: 100% !important; min-width: 0; table-layout: auto; }
|
|
1180
1322
|
.fx-table--sized td { white-space: normal; }
|
|
1181
1323
|
.fx-table--sized colgroup { display: none; }
|
|
1182
1324
|
.fx-table--sized td.fx-c-end { display: none; }
|
|
1183
|
-
|
|
1325
|
+
/* Tables outside a column list still stack. People keeps its own grid. */
|
|
1326
|
+
.fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table,
|
|
1327
|
+
.fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table tbody,
|
|
1328
|
+
.fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table tr,
|
|
1329
|
+
.fx-table-scroll:not([data-phone-layout="two-line"]) .fx-table td,
|
|
1330
|
+
.fx-table:not(.fx-table--sized, .fx-people),
|
|
1331
|
+
.fx-table:not(.fx-table--sized, .fx-people) tbody,
|
|
1332
|
+
.fx-table:not(.fx-table--sized, .fx-people) tr,
|
|
1333
|
+
.fx-table:not(.fx-table--sized, .fx-people) td { display: block; width: 100%; }
|
|
1184
1334
|
.fx-table tr { padding: 12px 14px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
1185
1335
|
.fx-table td { padding: 4px 0; border: 0; }
|
|
1186
1336
|
.fx-table td:nth-child(2), .fx-table td:nth-child(3) { display: inline-flex; width: auto; margin: 6px 10px 0 0; vertical-align: middle; }
|
|
@@ -1197,20 +1347,78 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1197
1347
|
.fx-people td:nth-child(2) .fx-select { width: 100%; }
|
|
1198
1348
|
.fx-people td:nth-child(3) { grid-area: 3 / 1 / 4 / -1; }
|
|
1199
1349
|
.fx-people td:nth-child(3):empty { display: none; }
|
|
1200
|
-
/*
|
|
1201
|
-
and
|
|
1202
|
-
with
|
|
1203
|
-
.fx-table
|
|
1204
|
-
|
|
1205
|
-
.fx-table
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
.fx-table
|
|
1212
|
-
|
|
1213
|
-
|
|
1350
|
+
/* Column lists (columns.js): two lines, 56px. Line 1 is the title columns
|
|
1351
|
+
and the amount. Line 2 is the status and up to three meta columns.
|
|
1352
|
+
The tick stays at the left with a 44px hit area. */
|
|
1353
|
+
[data-phone-layout="two-line"] .fx-table,
|
|
1354
|
+
[data-phone-layout="two-line"] .fx-table tbody { display: block; width: 100% !important; min-width: 0; }
|
|
1355
|
+
[data-phone-layout="two-line"] .fx-table tbody tr:not(.fx-table__group) {
|
|
1356
|
+
display: flex; flex-wrap: wrap; align-items: center; position: relative;
|
|
1357
|
+
box-sizing: border-box; height: 56px; min-height: 56px; max-height: 56px;
|
|
1358
|
+
margin: 0; padding: 6px 12px 6px 44px; overflow: hidden;
|
|
1359
|
+
border-bottom: 1px solid var(--fx-line-soft);
|
|
1360
|
+
}
|
|
1361
|
+
[data-phone-layout="two-line"] .fx-table tbody tr:not(.fx-table__group)::after {
|
|
1362
|
+
content: ""; order: 3; flex-basis: 100%; height: 0;
|
|
1363
|
+
}
|
|
1364
|
+
[data-phone-layout="two-line"] .fx-table td,
|
|
1365
|
+
[data-phone-layout="two-line"] .fx-table td:nth-child(2),
|
|
1366
|
+
[data-phone-layout="two-line"] .fx-table td:nth-child(3) {
|
|
1367
|
+
display: block; width: auto; margin: 0; padding: 0; border: 0;
|
|
1368
|
+
white-space: nowrap; background: none;
|
|
1369
|
+
}
|
|
1370
|
+
[data-phone-layout="two-line"] .fx-tick-cell {
|
|
1371
|
+
position: absolute; top: 0; left: 0; display: flex; align-items: center; justify-content: center;
|
|
1372
|
+
width: 44px; height: 56px; padding: 0;
|
|
1373
|
+
}
|
|
1374
|
+
[data-phone-layout="two-line"] .fx-tick-cell .fx-tick { width: 44px; height: 44px; margin: 0; }
|
|
1375
|
+
[data-phone-layout="two-line"] [data-phone-slot="title"] {
|
|
1376
|
+
order: 1; flex: 1 1 0; min-width: 0; overflow: hidden; text-overflow: ellipsis;
|
|
1377
|
+
color: var(--t-strong); font-weight: 600;
|
|
1378
|
+
}
|
|
1379
|
+
[data-phone-layout="two-line"] [data-phone-slot="title"] :is(a, button) {
|
|
1380
|
+
display: inline-flex; align-items: center; max-width: 100%; min-height: 44px;
|
|
1381
|
+
}
|
|
1382
|
+
[data-phone-layout="two-line"] [data-phone-slot="end"] {
|
|
1383
|
+
order: 2; flex: none; margin-left: 8px; font-variant-numeric: tabular-nums;
|
|
1384
|
+
}
|
|
1385
|
+
[data-phone-layout="two-line"] [data-phone-slot="meta"] {
|
|
1386
|
+
order: 5; flex: 0 1 auto; min-width: 0; max-width: 42%; overflow: hidden; text-overflow: ellipsis;
|
|
1387
|
+
color: var(--t-dim); font-size: 12px; line-height: 16px;
|
|
1388
|
+
}
|
|
1389
|
+
[data-phone-layout="two-line"] td.fx-c-stage,
|
|
1390
|
+
[data-phone-layout="two-line"] td.fx-c-status { order: 4; color: inherit; }
|
|
1391
|
+
[data-phone-layout="two-line"] .fx-stage { padding-block: 0; line-height: 16px; }
|
|
1392
|
+
[data-phone-layout="two-line"] [data-phone-slot="hide"],
|
|
1393
|
+
[data-phone-layout="two-line"] td.fx-c-end { display: none; }
|
|
1394
|
+
[data-phone-layout="two-line"] tr.fx-table__group {
|
|
1395
|
+
display: flex; align-items: center; box-sizing: border-box; height: 32px; min-height: 32px; max-height: 32px;
|
|
1396
|
+
margin: 0; padding: 0 8px 0 44px; overflow: visible; background: var(--t-s1);
|
|
1397
|
+
border-bottom: 1px solid var(--fx-line-soft);
|
|
1398
|
+
}
|
|
1399
|
+
[data-phone-layout="two-line"] tr.fx-table__group td { height: 32px; padding: 0; background: none; }
|
|
1400
|
+
[data-phone-layout="two-line"] tr.fx-table__group .fx-tick-cell { height: 32px; }
|
|
1401
|
+
[data-phone-layout="two-line"] tr.fx-table__group .fx-tick-cell .fx-tick { width: 44px; height: 44px; }
|
|
1402
|
+
/* Tiles: every card is one 56px row, as a list row: the mark, the name,
|
|
1403
|
+
the status and ›. The blurb and the button stay on the page it opens. */
|
|
1404
|
+
.fx-int-grid, .fx-tiles__grid { gap: 8px; }
|
|
1405
|
+
:is(.fx-int-card, .fx-tile)[data-phone-layout="one-line"] {
|
|
1406
|
+
display: flex; align-items: center; gap: 12px; box-sizing: border-box;
|
|
1407
|
+
height: 56px; padding: 0 8px 0 12px;
|
|
1408
|
+
}
|
|
1409
|
+
.fx-int-card[data-phone-layout="one-line"]:is(:hover, :focus-visible) { transform: none; box-shadow: none; }
|
|
1410
|
+
[data-phone-layout="one-line"] .fx-int-card__head { display: contents; }
|
|
1411
|
+
[data-phone-layout="one-line"] :is(.fx-int-card__blurb, .fx-int-card__btn) { display: none; }
|
|
1412
|
+
[data-phone-layout="one-line"] .fx-logo { width: 32px; height: 32px; border-radius: var(--t-radius); }
|
|
1413
|
+
[data-phone-layout="one-line"] .fx-logo img { width: 20px; height: 20px; }
|
|
1414
|
+
[data-phone-layout="one-line"] .fx-logo--icon .fx-lucide { width: 18px; height: 18px; }
|
|
1415
|
+
[data-phone-layout="one-line"] h3 {
|
|
1416
|
+
flex: 1; min-width: 0; overflow: hidden; font: 600 14.5px/1.35 var(--t-body);
|
|
1417
|
+
text-overflow: ellipsis; white-space: nowrap;
|
|
1418
|
+
}
|
|
1419
|
+
[data-phone-layout="one-line"] :is(.fx-status, .fx-tile__meta) { flex: none; }
|
|
1420
|
+
[data-phone-layout="one-line"] .fx-tile__go { display: inline-flex; flex: none; color: var(--t-dim); }
|
|
1421
|
+
[data-phone-layout="one-line"] .fx-tile__go .fx-lucide { width: 18px; height: 18px; }
|
|
1214
1422
|
}
|
|
1215
1423
|
|
|
1216
1424
|
@keyframes fx-rise { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
@@ -1239,7 +1447,10 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1239
1447
|
.fx-price { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
|
|
1240
1448
|
.fx-price__head { display: flex; flex-wrap: wrap; align-items: end; justify-content: space-between; gap: 8px 16px; }
|
|
1241
1449
|
.fx-price__head .fx-search { margin: 0 0 6px; width: 260px; }
|
|
1242
|
-
@media (max-width: 639px) {
|
|
1450
|
+
@media (max-width: 639px) {
|
|
1451
|
+
.fx-price__head .fx-search { width: 100%; }
|
|
1452
|
+
.fx-price__settings .fx-select { min-height: 44px; }
|
|
1453
|
+
}
|
|
1243
1454
|
.fx-price__table th { position: sticky; top: 0; z-index: 1; }
|
|
1244
1455
|
.fx-price__table td { padding-top: 9px; padding-bottom: 9px; }
|
|
1245
1456
|
.fx-price__table td.fx-num, .fx-price__table th.fx-num { text-align: right; white-space: nowrap; }
|
|
@@ -1411,13 +1622,27 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1411
1622
|
.fx-cfs__delete { color: var(--t-danger); background: color-mix(in srgb, var(--t-danger) 12%, transparent); }
|
|
1412
1623
|
.fx-cfs__delete:hover { background: color-mix(in srgb, var(--t-danger) 28%, transparent); }
|
|
1413
1624
|
.fx-cfs__delete .fx-lucide { width: 16px; height: 16px; }
|
|
1414
|
-
@media (max-width:
|
|
1625
|
+
@media (max-width: 639px) {
|
|
1415
1626
|
.fx-cfm { grid-template-columns: minmax(0, 1fr) 36px 32px; column-gap: 8px; }
|
|
1416
1627
|
.fx-cfm .fx-cfm__by, .fx-cfm .fx-cfm__date, .fx-cfm .fx-cfm__where { display: none; }
|
|
1417
1628
|
.fx-cfm__head, .fx-cfm__row { padding-left: 16px; }
|
|
1418
1629
|
.fx-cfm__create { padding-left: 16px; }
|
|
1419
1630
|
.fx-cfm__search { flex: 1 1 100%; width: auto; }
|
|
1420
1631
|
.fx-cfm__edit { display: none !important; }
|
|
1632
|
+
/* Custom fields uses the same phone slots as a column list: name and the
|
|
1633
|
+
menu on line 1, up to three facts on line 2, 56px. */
|
|
1634
|
+
.fx-cfm__row {
|
|
1635
|
+
display: flex; flex-wrap: wrap; align-items: center; box-sizing: border-box;
|
|
1636
|
+
height: 56px; min-height: 56px; max-height: 56px; padding: 6px 8px 6px 16px; overflow: hidden;
|
|
1637
|
+
}
|
|
1638
|
+
.fx-cfm__row::after { content: ""; order: 3; flex-basis: 100%; height: 0; }
|
|
1639
|
+
.fx-cfm__row > * { display: none; }
|
|
1640
|
+
.fx-cfm__row > [data-phone-slot="title"] { display: block; order: 1; flex: 1 1 0; min-width: 0; }
|
|
1641
|
+
.fx-cfm__row > [data-phone-slot="end"] { display: flex; order: 2; flex: none; align-items: center; }
|
|
1642
|
+
.fx-cfm__row > [data-phone-slot="meta"] {
|
|
1643
|
+
display: block; order: 5; flex: 0 1 auto; min-width: 0; max-width: 40%; overflow: hidden;
|
|
1644
|
+
text-overflow: ellipsis; white-space: nowrap; color: var(--t-dim); font-size: 12px; line-height: 16px;
|
|
1645
|
+
}
|
|
1421
1646
|
}
|
|
1422
1647
|
/* Invoice rules (invoice-rules.js): a row's Edit button and switch, and the
|
|
1423
1648
|
rule dialog's condition lines. */
|
|
@@ -1556,6 +1781,8 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1556
1781
|
}
|
|
1557
1782
|
.fx-bulk[hidden] { display: none; }
|
|
1558
1783
|
.fx-bulk[aria-busy="true"] { opacity: .7; pointer-events: none; }
|
|
1784
|
+
.fx-bulk[aria-busy="true"]:has([data-busy]) { opacity: 1; }
|
|
1785
|
+
.fx-bulk__act .fx-spinner { flex: none; border-color: color-mix(in srgb, currentColor 35%, transparent); border-top-color: currentColor; }
|
|
1559
1786
|
.fx-bulk__acts { display: flex; align-items: center; gap: 2px; min-width: 0; }
|
|
1560
1787
|
.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; }
|
|
1561
1788
|
.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; }
|
|
@@ -1584,6 +1811,11 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1584
1811
|
.fx-bulk__sep { margin: 0 2px; }
|
|
1585
1812
|
.fx-bulk__act:not(.fx-bulk__act--primary) .fx-bulk__word { display: none; }
|
|
1586
1813
|
}
|
|
1814
|
+
/* Phone and tablet: Delete and Void are a full touch target. */
|
|
1815
|
+
@media (max-width: 1023px) {
|
|
1816
|
+
.fx-invoice .fx-ph__actions .fx-btn--danger { min-height: 44px; }
|
|
1817
|
+
.fx-bulk__act[data-tone="danger"] { min-width: 44px; min-height: 44px; justify-content: center; }
|
|
1818
|
+
}
|
|
1587
1819
|
|
|
1588
1820
|
/* Xero › Import (server/ui/xero-import.js): Review compares one fact per row
|
|
1589
1821
|
with ✓ or ≠ between the sides; the list and history tick rows for the bulk
|
|
@@ -1699,3 +1931,22 @@ body[data-state="loading"] .fx-loading { display: grid; justify-items: center; }
|
|
|
1699
1931
|
.fx-xu-hour { flex: none; width: 10px; }
|
|
1700
1932
|
.fx-xu__fold { padding: 12px; }
|
|
1701
1933
|
}
|
|
1934
|
+
|
|
1935
|
+
/* Contacts (server/ui/contacts.js) and the pending-invoice Create offer.
|
|
1936
|
+
Phone targets stay on this page only, so other screenshots stay valid. */
|
|
1937
|
+
.fx-offer { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
|
|
1938
|
+
.fx-offer > .fx-notice { flex: 1 1 220px; }
|
|
1939
|
+
.fx-contacts { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
|
|
1940
|
+
.fx-contacts__tools { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--fx-line-soft); }
|
|
1941
|
+
.fx-contacts__search { flex: 1 1 180px; max-width: 320px; }
|
|
1942
|
+
.fx-contacts .fx-card { padding: 16px 20px; }
|
|
1943
|
+
.fx-contacts__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
|
1944
|
+
.fx-contacts__wide { grid-column: 1 / -1; }
|
|
1945
|
+
.fx-contacts textarea.fx-input { min-height: 96px; padding: 8px 10px; resize: vertical; }
|
|
1946
|
+
.fx-contacts .fx-person { color: inherit; text-decoration: none; }
|
|
1947
|
+
@media (max-width: 639px) {
|
|
1948
|
+
.fx-contacts__grid { grid-template-columns: minmax(0, 1fr); }
|
|
1949
|
+
.fx-offer .fx-btn,
|
|
1950
|
+
.fx-contacts :is(.fx-btn, .fx-input, .fx-select, .fx-check) { min-height: 44px; }
|
|
1951
|
+
.fx-contacts .fx-help { min-width: 44px; min-height: 44px; }
|
|
1952
|
+
}
|