fsanyoto commited on
Commit
5799f9f
Β·
verified Β·
1 Parent(s): 754c233

Deploy AIOS web (React glide grid + FastAPI slice)

Browse files
Files changed (4) hide show
  1. RELEASES.json +7 -1
  2. VERSION +1 -1
  3. web/src/customer-grid/MapView.tsx +146 -119
  4. web/src/index.css +26 -0
RELEASES.json CHANGED
@@ -1,6 +1,12 @@
1
  {
2
- "current": "v47 (917f9a0)",
3
  "releases": [
 
 
 
 
 
 
4
  {
5
  "version": "v47",
6
  "sha": "917f9a0",
 
1
  {
2
+ "current": "v48 (3c69fd9)",
3
  "releases": [
4
+ {
5
+ "version": "v48",
6
+ "sha": "3c69fd9",
7
+ "date": "2026-08-23",
8
+ "subject": "release v48"
9
+ },
10
  {
11
  "version": "v47",
12
  "sha": "917f9a0",
VERSION CHANGED
@@ -1 +1 @@
1
- v47 (917f9a0)
 
1
+ v48 (3c69fd9)
web/src/customer-grid/MapView.tsx CHANGED
@@ -2039,114 +2039,15 @@ export function MapView({
2039
  ))}
2040
  </select>
2041
  </label>
2042
- {/* ── ITEM 4: the hand-off links, ONE PER LINE, DIRECTLY under the visit order
2043
- field ────────────────────────────────────────────────────────────────────
2044
- They were a `flex-wrap` cluster of chips, unreadable the moment a route
2045
- needs more than one Google leg. Stacked, each on its own row, the list reads
2046
- as what it is: leg 1, then leg 2.
2047
- ⚠ POSITION IS PART OF THE INSTRUCTION, not a detail. They sit between the
2048
- select and its Save button rather than after the whole block, because the
2049
- block's tail can carry a stale marker, a shared-orders note and two message
2050
- rows, and "under the Visit order column" stops being true the moment three
2051
- of those render between the two. */}
2052
- {plan.links.length > 0 && (
2053
- <div className="cg-map-google-links" aria-label="Google Maps route links">
2054
- {plan.links.map((link) => (
2055
- <a
2056
- key={link.index}
2057
- href={link.url}
2058
- target="_blank"
2059
- rel="noopener noreferrer"
2060
- title={`Open Google Maps route ${link.index} in a new tab`}
2061
- >
2062
- Google Maps route {link.index}
2063
- </a>
2064
- ))}
2065
- </div>
2066
- )}
2067
- {/* ── ITEMS 4 AND 5: "Save route", and it ASKS FOR THE NAME ─────────────────
2068
- ⭐ ITEM 4 β€” the button read "Save visit order". Owner: *"Change the button
2069
- called 'Save visit order' to 'Save route'."* The FIELD is still the visit
2070
- order (that is what the numbers in it are); the ACT is saving a route.
2071
- ⭐ ITEM 5 β€” a NEW field now opens a name box first, pre-filled with the
2072
- server's next free "Route N". Updating a field that already exists does not
2073
- prompt: it already has a name, and asking again would invite a rename
2074
- through a door that re-solves numbers rather than through Edit field.
2075
- β›” AN INLINE STEP, NOT A BROWSER `prompt()`. A modal dialog blocks the page
2076
- and cannot be styled, read by the design system, or dismissed with Escape
2077
- the way every other overlay here is. */}
2078
- {naming && !saveTarget ? (
2079
- <div className="cg-route-name" role="group" aria-label="Name this route">
2080
- <input
2081
- className="cg-input"
2082
- autoFocus
2083
- value={nameDraft}
2084
- aria-label="Route name"
2085
- placeholder={nextLabel || "Name this route"}
2086
- maxLength={120}
2087
- onChange={(e) => setNameDraft(e.target.value)}
2088
- onKeyDown={(e) => {
2089
- if (e.key === "Enter") { e.preventDefault(); void saveRouteOrder(nameDraft); }
2090
- if (e.key === "Escape") { e.preventDefault(); setNaming(false); }
2091
- }}
2092
- />
2093
- <span className="cg-route-name-actions">
2094
- <button
2095
- type="button"
2096
- className="cg-btn cg-btn--primary"
2097
- disabled={saveBusy || !nameDraft.trim()}
2098
- onClick={() => void saveRouteOrder(nameDraft)}
2099
- title="Save these visit numbers into a new field with this name"
2100
- >
2101
- {saveBusy ? "Saving" : "Save route"}
2102
- </button>
2103
- <button
2104
- type="button"
2105
- className="cg-btn"
2106
- onClick={() => setNaming(false)}
2107
- title="Do not save this route yet"
2108
- >
2109
- Cancel
2110
- </button>
2111
- </span>
2112
- </div>
2113
- ) : (
2114
- <button
2115
- type="button"
2116
- className="cg-btn cg-btn--primary cg-route-cta"
2117
- disabled={saveBusy}
2118
- onClick={() => {
2119
- if (saveTarget) { void saveRouteOrder(); return; }
2120
- setNameDraft(nextLabel);
2121
- setSaveErr(null);
2122
- setSaveMsg(null);
2123
- setNaming(true);
2124
- }}
2125
- title={
2126
- saveTarget
2127
- ? "Write these visit numbers into the saved field, for everyone it is shared with"
2128
- : "Name this route and save these visit numbers as a new field"
2129
- }
2130
- >
2131
- {saveBusy ? "Saving" : saveTarget ? "Update route" : "Save route"}
2132
- </button>
2133
- )}
2134
- {/* β›” DERIVED, NEVER STORED. The saved field carries the fingerprint of the
2135
- inputs it was solved from; this compares it against the cohort ON SCREEN
2136
- NOW, so the marker cannot itself go out of date. */}
2137
- {orderStale && (
2138
- <span className="cg-map-route-note">
2139
- Stale. The saved numbers were solved from different stops, so re-solving
2140
- would change them.
2141
- </span>
2142
- )}
2143
  {(routeCols || []).some((f) => !f.mine) && (
2144
  <span className="cg-map-route-note">
2145
  Shared orders you can read but not re-solve are not listed above.
2146
  </span>
2147
  )}
2148
- {saveMsg && <span className="cg-map-route-note">{saveMsg}</span>}
2149
- {saveErr && <span className="cg-cal-nodate">{saveErr}</span>}
2150
  </>
2151
  )}
2152
 
@@ -2273,23 +2174,149 @@ export function MapView({
2273
  </span>
2274
  )}
2275
 
2276
- {routeOn && (
2277
- <button
2278
- type="button"
2279
- className="cg-btn cg-btn--danger cg-route-cta"
2280
- onClick={() => {
2281
- setRouteOn(false);
2282
- setRouteStartPid(null);
2283
- setRouteOrder(null);
2284
- setRoad(null);
2285
- setRoadErr(null);
2286
- setDepot(null);
2287
- }}
2288
- title="Drop this route and start again. The records stay selected."
2289
- >
2290
- Clear route
2291
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2292
  )}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2293
  </div>
2294
  </aside>
2295
  )}
 
2039
  ))}
2040
  </select>
2041
  </label>
2042
+ {/* ⚠ THIS NOTE STAYS WITH THE SELECT, while items 8 and 9 send the Save
2043
+ button and the Google links to the foot. It is a statement about what
2044
+ the LIST ABOVE does not contain ("not listed above"), so it is the one
2045
+ line in the old block that would become false the moment it travelled. */}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2046
  {(routeCols || []).some((f) => !f.mine) && (
2047
  <span className="cg-map-route-note">
2048
  Shared orders you can read but not re-solve are not listed above.
2049
  </span>
2050
  )}
 
 
2051
  </>
2052
  )}
2053
 
 
2174
  </span>
2175
  )}
2176
 
2177
+ {/* ══ OWNER ITEMS 8 AND 9 (2026-08-23) β€” THE COMMITTING CONTROLS LIVE AT THE FOOT ══
2178
+ Owner: *"the 'Google Map Route 1' etc. should still be located at the bottom
2179
+ just above clear route button. And the save route button should still be at
2180
+ the bottom too beside clear route button."*
2181
+ β›”β›” THIS OVERRULES THE OLDER "POSITION IS PART OF THE INSTRUCTION" NOTE, WHICH
2182
+ IS KEPT NOWHERE ELSE, SO IT IS RESTATED HERE RATHER THAN DELETED. Item 4 of the
2183
+ earlier pass put the Google links BETWEEN the visit-order select and its Save
2184
+ button, so that "under the Visit order column" stayed true; item 3b then moved
2185
+ that whole block above Start. The owner has now separated the two jobs: the
2186
+ select is SETUP and belongs at the top with Add a record, while saving and
2187
+ clearing are the two ways to FINISH and belong together at the bottom. The old
2188
+ argument was about keeping the links adjacent to something; they are now
2189
+ adjacent to the button that commits them, which serves it.
2190
+ ⚠ The Save button and Clear route share one row (item 9's "beside"), so
2191
+ `.cg-route-cta`'s full-width override is deliberately NOT applied to them β€”
2192
+ two full-width buttons cannot sit beside each other. */}
2193
+ {plan && (
2194
+ <>
2195
+ {/* ── ITEM 4: the hand-off links, ONE PER LINE ─────────────────────────
2196
+ They were a `flex-wrap` cluster of chips, unreadable the moment a route
2197
+ needs more than one Google leg. Stacked, each on its own row, the list
2198
+ reads as what it is: leg 1, then leg 2. Owner item 8 moved the stack down
2199
+ here, directly above the two buttons that finish the job. */}
2200
+ {plan.links.length > 0 && (
2201
+ <div className="cg-map-google-links" aria-label="Google Maps route links">
2202
+ {plan.links.map((link) => (
2203
+ <a
2204
+ key={link.index}
2205
+ href={link.url}
2206
+ target="_blank"
2207
+ rel="noopener noreferrer"
2208
+ title={`Open Google Maps route ${link.index} in a new tab`}
2209
+ >
2210
+ Google Maps route {link.index}
2211
+ </a>
2212
+ ))}
2213
+ </div>
2214
+ )}
2215
+ {/* ── ITEMS 4 AND 5: "Save route", and it ASKS FOR THE NAME ─────────────────
2216
+ ⭐ ITEM 4 β€” the button read "Save visit order". Owner: *"Change the button
2217
+ called 'Save visit order' to 'Save route'."* The FIELD is still the visit
2218
+ order (that is what the numbers in it are); the ACT is saving a route.
2219
+ ⭐ ITEM 5 β€” a NEW field now opens a name box first, pre-filled with the
2220
+ server's next free "Route N". Updating a field that already exists does not
2221
+ prompt: it already has a name, and asking again would invite a rename
2222
+ through a door that re-solves numbers rather than through Edit field.
2223
+ β›” AN INLINE STEP, NOT A BROWSER `prompt()`. A modal dialog blocks the page
2224
+ and cannot be styled, read by the design system, or dismissed with Escape
2225
+ the way every other overlay here is. */}
2226
+ {naming && !saveTarget && (
2227
+ <div className="cg-route-name" role="group" aria-label="Name this route">
2228
+ <input
2229
+ className="cg-input"
2230
+ autoFocus
2231
+ value={nameDraft}
2232
+ aria-label="Route name"
2233
+ placeholder={nextLabel || "Name this route"}
2234
+ maxLength={120}
2235
+ onChange={(e) => setNameDraft(e.target.value)}
2236
+ onKeyDown={(e) => {
2237
+ if (e.key === "Enter") { e.preventDefault(); void saveRouteOrder(nameDraft); }
2238
+ if (e.key === "Escape") { e.preventDefault(); setNaming(false); }
2239
+ }}
2240
+ />
2241
+ <span className="cg-route-name-actions">
2242
+ <button
2243
+ type="button"
2244
+ className="cg-btn cg-btn--primary"
2245
+ disabled={saveBusy || !nameDraft.trim()}
2246
+ onClick={() => void saveRouteOrder(nameDraft)}
2247
+ title="Save these visit numbers into a new field with this name"
2248
+ >
2249
+ {saveBusy ? "Saving" : "Save route"}
2250
+ </button>
2251
+ <button
2252
+ type="button"
2253
+ className="cg-btn"
2254
+ onClick={() => setNaming(false)}
2255
+ title="Do not save this route yet"
2256
+ >
2257
+ Cancel
2258
+ </button>
2259
+ </span>
2260
+ </div>
2261
+ )}
2262
+ {/* β›” DERIVED, NEVER STORED. The saved field carries the fingerprint of the
2263
+ inputs it was solved from; this compares it against the cohort ON SCREEN
2264
+ NOW, so the marker cannot itself go out of date. */}
2265
+ {orderStale && (
2266
+ <span className="cg-map-route-note">
2267
+ Stale. The saved numbers were solved from different stops, so re-solving
2268
+ would change them.
2269
+ </span>
2270
+ )}
2271
+ {saveMsg && <span className="cg-map-route-note">{saveMsg}</span>}
2272
+ {saveErr && <span className="cg-cal-nodate">{saveErr}</span>}
2273
+ </>
2274
  )}
2275
+
2276
+ <div className="cg-route-foot">
2277
+ {/* ⚠ THE NAME BOX IS FULL WIDTH ABOVE THE ROW, NOT INSIDE IT. A text input, a
2278
+ Save and a Cancel cannot share a 338px row with Clear route without every
2279
+ one of them shrinking past reading width, so while it is open the row shows
2280
+ Clear route alone and the prompt owns the line above it. */}
2281
+ {plan && !(naming && !saveTarget) && (
2282
+ <button
2283
+ type="button"
2284
+ className="cg-btn cg-btn--primary cg-route-foot-btn"
2285
+ disabled={saveBusy}
2286
+ onClick={() => {
2287
+ if (saveTarget) { void saveRouteOrder(); return; }
2288
+ setNameDraft(nextLabel);
2289
+ setSaveErr(null);
2290
+ setSaveMsg(null);
2291
+ setNaming(true);
2292
+ }}
2293
+ title={
2294
+ saveTarget
2295
+ ? "Write these visit numbers into the saved field, for everyone it is shared with"
2296
+ : "Name this route and save these visit numbers as a new field"
2297
+ }
2298
+ >
2299
+ {saveBusy ? "Saving" : saveTarget ? "Update route" : "Save route"}
2300
+ </button>
2301
+ )}
2302
+ {routeOn && (
2303
+ <button
2304
+ type="button"
2305
+ className="cg-btn cg-btn--danger cg-route-cta"
2306
+ onClick={() => {
2307
+ setRouteOn(false);
2308
+ setRouteStartPid(null);
2309
+ setRouteOrder(null);
2310
+ setRoad(null);
2311
+ setRoadErr(null);
2312
+ setDepot(null);
2313
+ }}
2314
+ title="Drop this route and start again. The records stay selected."
2315
+ >
2316
+ Clear route
2317
+ </button>
2318
+ )}
2319
+ </div>
2320
  </div>
2321
  </aside>
2322
  )}
web/src/index.css CHANGED
@@ -5086,6 +5086,20 @@ a.cg-map-ctl-b { text-decoration: none; }
5086
  gap: 6px;
5087
  }
5088
  .cg-route-name-actions .cg-btn { flex: 1 1 0; justify-content: center; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5089
  .cg-route-popover .cg-map-route-sum { white-space: normal; line-height: 1.45; }
5090
  .cg-route-popover .cg-map-route-opt {
5091
  align-items: stretch;
@@ -5195,6 +5209,18 @@ a.cg-map-ctl-b { text-decoration: none; }
5195
  scrollbar-width: thin;
5196
  }
5197
  .cg-route-picker-opt {
 
 
 
 
 
 
 
 
 
 
 
 
5198
  padding: 5px 6px;
5199
  border: 0;
5200
  border-radius: 4px;
 
5086
  gap: 6px;
5087
  }
5088
  .cg-route-name-actions .cg-btn { flex: 1 1 0; justify-content: center; }
5089
+ /* ⭐ OWNER ITEMS 8 AND 9 (2026-08-23) β€” SAVE AND CLEAR SHARE THE LAST ROW. Owner: *"the save
5090
+ route button should still be at the bottom too beside clear route button."* The Google links sit
5091
+ directly above it (item 8), so the panel ends: how to hand this route to Google, then the two
5092
+ ways to finish with it.
5093
+ β›” `.cg-route-cta`'s `width: 100%` is OVERRIDDEN here rather than simply not applied, because
5094
+ `Clear route` still carries that class from every other position it has ever had. Two full-width
5095
+ buttons cannot sit beside each other, which is the whole of item 9. */
5096
+ .cg-route-foot {
5097
+ display: flex;
5098
+ gap: 6px;
5099
+ width: 100%;
5100
+ margin-top: 2px;
5101
+ }
5102
+ .cg-route-foot .cg-btn { flex: 1 1 0; width: auto; justify-content: center; text-align: center; }
5103
  .cg-route-popover .cg-map-route-sum { white-space: normal; line-height: 1.45; }
5104
  .cg-route-popover .cg-map-route-opt {
5105
  align-items: stretch;
 
5209
  scrollbar-width: thin;
5210
  }
5211
  .cg-route-picker-opt {
5212
+ /* β›”β›” OWNER ITEM 10 (2026-08-23) β€” `flex: 0 0 auto` IS THE WHOLE FIX, AND ITS ABSENCE IS WHY
5213
+ THE RESULTS PILED ON TOP OF EACH OTHER. Owner: *"when i type search records for the route, the
5214
+ dropdown overlays for both start and search database."* The list is a flex COLUMN with
5215
+ `max-height: 168px`, and a flex item's default `flex-shrink: 1` lets the browser squash it
5216
+ below its content height to fit β€” so 40 results in a 168px box became 40 four-pixel rows,
5217
+ each one's text spilling over its neighbour's. REPRODUCED before it was fixed: 662 matches
5218
+ for "flow", the panel painting an unreadable stack of half-overlapping names.
5219
+ ⚠ The `overflow-y: auto` on the list was never the problem and is not the fix: a scroller
5220
+ only scrolls what it is allowed to leave at full height. It could not, so it never scrolled.
5221
+ ⚠ Applies to BOTH pickers by construction (Start and Add a record are one component), which
5222
+ is what the owner's "for both" describes. */
5223
+ flex: 0 0 auto;
5224
  padding: 5px 6px;
5225
  border: 0;
5226
  border-radius: 4px;