Skip to content

Commit a3a7164

Browse files
Merge pull request #118 from niteeshkanna-sh/claude/stoic-rubin-fnglq7
A heading for every fact, and the car the visitor actually chose
2 parents c1f8031 + 778a918 commit a3a7164

4 files changed

Lines changed: 139 additions & 113 deletions

File tree

‎admin/admin.css‎

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1427,49 +1427,29 @@ label.finance-toggle {
14271427
cost the row a line for nothing. */
14281428
.rec-table .rec-when { white-space: nowrap; }
14291429

1430-
/* A cell laid out across instead of down, and kept that way.
1431-
1432-
It used to wrap when the panel ran out of room, which kept the table inside
1433-
the window at the cost of the thing the layout was for: at 1440 the rows
1434-
folded back to two lines and no two rows folded in the same place, so the
1435-
columns stopped lining up. The box above scrolls instead. A row is one line
1436-
at every width now, every row the same height, and the eye can run down a
1437-
column.
1438-
1439-
Nothing inside breaks either: half a phone number on each of two lines is
1440-
worse than either half. */
1441-
.rec-table .rec-line {
1442-
display: flex; align-items: baseline;
1443-
gap: 7px; min-width: 0; white-space: nowrap;
1444-
}
1445-
.rec-table .rec-line > * { flex: none; white-space: nowrap; }
1446-
1447-
/* One part may give, and it is the name: a name is still recognisable from
1448-
its first eighteen characters (the whole of it is on the title attribute),
1449-
and without a ceiling one long one widens the column for every row. */
1450-
.rec-table .rec-line > .rec-clip {
1451-
flex: 0 1 auto; min-width: 0; max-width: 18ch; overflow: hidden; text-overflow: ellipsis;
1452-
}
1453-
1454-
/* Its own element rather than a ::before on the detail, for two reasons: the
1455-
name's overflow:hidden would have clipped a pseudo-element off the end of a
1456-
truncated name, and on the rare line that wraps a separator left behind at
1457-
the end of the first line reads as "continues below", where one leading the
1458-
second line just reads as a mistake. */
1459-
.rec-table .rec-dot { color: var(--ink-faint); }
1460-
.rec-table .rec-note { color: var(--ink-dim); font-size: 0.82rem; }
1461-
1462-
/* Two pills read as one unit, and sit centred on each other rather than on a
1463-
baseline their padding has already moved. */
1464-
.rec-table .rec-line-chips { align-items: center; gap: 6px; }
1465-
/* The actions stay against the right edge while the rest of the row slides
1466-
under them. They are the reason most people are looking at the list at all,
1467-
and hunting for them by scrolling is a strange thing to ask.
1468-
1469-
Opaque, because the row passes behind it -- including on hover, which the
1470-
row's own background change would otherwise leave stranded here. The rule
1471-
down its left edge is what says the column is pinned rather than merely
1472-
last. */
1430+
/* Every cell is one line. Each fact has its own column now, so there is
1431+
nothing left in a cell to fold against -- and the box above scrolls, which
1432+
is what pays for the width. */
1433+
.rec-table td { white-space: nowrap; }
1434+
1435+
/* One part may give, and it is a name: a name is still recognisable from its
1436+
first twenty characters (the whole of it is on the title attribute), and
1437+
without a ceiling one long one widens the column for every row. A block
1438+
rather than inline so the ellipsis has a box to happen in. */
1439+
.rec-table .rec-clip {
1440+
display: block; max-width: 20ch; overflow: hidden; text-overflow: ellipsis;
1441+
}
1442+
1443+
/* Something the enquiry did not say -- no car named, no dates given -- is
1444+
greyer than something it did. The words stay, because a dash does not tell
1445+
a new reader whether the question was asked. */
1446+
.rec-table .rec-none { color: var(--ink-faint); }
1447+
1448+
/* The only cell still holding two things, because they are one thing: the
1449+
recorded status and the position the dates put it in. Centred on each other
1450+
rather than on a baseline their padding has already moved. */
1451+
.rec-table .rec-line-chips { display: flex; align-items: center; gap: 6px; }
1452+
14731453
.rec-table .rec-act {
14741454
width: 1%; white-space: nowrap;
14751455
position: sticky; right: 0; z-index: 1;
@@ -1487,13 +1467,14 @@ label.finance-toggle {
14871467
/* The corner: pinned both ways, so it must sit above both. */
14881468
.rec-table thead .rec-act { z-index: 3; }
14891469
.rec-unread td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
1490-
/* On the last part of the name line rather than on the name itself. The name
1491-
clips with an ellipsis now, so a marker inside it would vanish exactly when
1492-
the name is long -- at random, from the reader's side. Hung off the last
1493-
part instead (the phone number, in practice) it travels with that part when
1494-
the line wraps, rather than dropping to a line of its own as a stray bullet,
1495-
which is what a flex item of its own did. */
1496-
.rec-unread .rec-line-name > :last-child::after {
1470+
/* On the enquiry number rather than on the name. The name clips with an
1471+
ellipsis, so a marker inside it would vanish exactly when the name is long
1472+
-- at random, from the reader's side. The number never clips.
1473+
1474+
It reads once at either width without a second rule: on a desk the number
1475+
has its own column, and on a phone that column is hidden and the only
1476+
.rec-id on the row is the copy printed under the name. */
1477+
.rec-unread .rec-id::after {
14971478
content: ''; display: inline-block; width: 7px; height: 7px; border-radius: 50%;
14981479
background: var(--accent); margin-left: 7px; vertical-align: middle;
14991480
}
@@ -1622,17 +1603,12 @@ label.finance-toggle {
16221603
.rec-only-sm { display: block; font-size: 0.76rem; }
16231604
.rec-table th, .rec-table td { padding: 12px 10px; }
16241605

1625-
/* Three columns of about 110 pixels. Nothing sits beside anything at that
1626-
width, so the line stacks outright rather than wrapping item by item --
1627-
the same shape it had before, and the same width, which is the width that
1628-
fits a phone. */
1629-
.rec-table .rec-line { flex-direction: column; align-items: flex-start; row-gap: 2px; }
1630-
/* And a long name breaks across lines here rather than setting the column's
1631-
width, which is the one thing that would push the table wider than the
1632-
phone it is on. */
1633-
.rec-table .rec-line > * { white-space: normal; }
1634-
.rec-table .rec-line > .rec-clip { max-width: none; }
1635-
.rec-table .rec-dot { display: none; }
1606+
/* Three columns of about 110 pixels each. A long name breaks across lines
1607+
here rather than setting the column's width, which is the one thing that
1608+
would push the table wider than the phone it is on. */
1609+
.rec-table td { white-space: normal; }
1610+
.rec-table .rec-clip { max-width: none; }
1611+
.rec-table .rec-line-chips { flex-wrap: wrap; }
16361612
}
16371613

16381614
/* ---- Are you sure? ------------------------------------------------------ */

‎admin/admin.js‎

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -637,33 +637,42 @@ async function renderInquiries() {
637637
columns: [
638638
{ key: 'enquiry_number', label: 'Inquiry No.', hide: true, sort: (e) => e.enquiry_number,
639639
cell: (e) => `<span class="rec-id">${escapeHTML(e.enquiry_number)}</span>` },
640-
// Across rather than down, the same as the bookings list. See the note
641-
// there; the two screens are read the same way and should look it.
640+
// One heading per thing. The name used to carry the phone number under
641+
// it and "Asked for" carried the hire dates, which left two of the
642+
// enquiry's six facts with no heading at all and put the hire dates
643+
// close enough to Received to be read as the same date. Separate
644+
// columns, and the box scrolls.
642645
{ key: 'name', label: 'Name', sort: (e) => e.name.toLowerCase(),
643-
cell: (e) => `<span class="rec-line rec-line-name">
644-
<span class="rec-who rec-clip" title="${escapeHTML(e.name)}"
645-
>${escapeHTML(e.name)}</span>
646-
${e.phone ? `<span class="rec-dot" aria-hidden="true">·</span>
647-
<span class="rec-note">${escapeHTML(e.phone)}</span>` : ''}
648-
</span>
646+
cell: (e) => `<span class="rec-who rec-clip" title="${escapeHTML(e.name)}"
647+
>${escapeHTML(e.name)}</span>
649648
<div class="rec-sub rec-id rec-only-sm">${escapeHTML(e.enquiry_number)}</div>` },
650-
{ key: 'vehicle_name', label: 'Asked for', hide: true, sort: (e) => (e.vehicle_name || '').toLowerCase(),
651-
cell: (e) => `<span class="rec-line">
652-
<span class="rec-clip">${escapeHTML(e.vehicle_name || 'Not specified')}</span>
653-
<span class="rec-dot" aria-hidden="true">·</span>
654-
<span class="rec-note">${e.start_date
655-
? (e.return_date
656-
? formatDateRange(e.start_date, e.return_date)
657-
: formatDate(e.start_date))
658-
: 'No dates given'}</span>
659-
</span>` },
649+
{ key: 'phone', label: 'Contact', hide: true, sort: (e) => e.phone || '',
650+
// Not a tel: link: the whole row opens the enquiry, and a link inside
651+
// it would fire both. The dialling is on the handset button at the end
652+
// of the row, where it cannot be hit by accident.
653+
cell: (e) => escapeHTML(e.phone || '—') },
654+
// The car the visitor chose, which the list could not see until the API
655+
// started sending it. See enquiry_vehicle_asked() for why the vehicle
656+
// join is usually empty and where the name really lives.
657+
{ key: 'vehicle_asked', label: 'Car', hide: true,
658+
sort: (e) => (e.vehicle_asked || '').toLowerCase(),
659+
cell: (e) => (e.vehicle_asked
660+
? `<span class="rec-clip" title="${escapeHTML(e.vehicle_asked)}"
661+
>${escapeHTML(e.vehicle_asked)}</span>`
662+
: '<span class="rec-none">Not specified</span>') },
663+
{ key: 'start_date', label: 'Hire dates', hide: true, sort: (e) => e.start_date || '',
664+
cell: (e) => (e.start_date
665+
? (e.return_date
666+
? formatDateRange(e.start_date, e.return_date)
667+
: formatDate(e.start_date))
668+
: '<span class="rec-none">Not given</span>') },
660669
{ key: 'status', label: 'Status', sort: (e) => e.status,
661-
cell: (e) => `<span class="rec-line rec-line-chips">
662-
<span class="status-badge status-badge-${e.status}">${e.status}</span>
663-
${e.booking_number
664-
? `<span class="rec-note">${escapeHTML(e.booking_number)}</span>` : ''}
665-
</span>` },
666-
{ key: 'created_at', label: 'Received', cls: 'rec-when', sort: (e) => e.created_at,
670+
cell: (e) => `<span class="status-badge status-badge-${e.status}">${e.status}</span>` },
671+
{ key: 'booking_number', label: 'Booking', hide: true, sort: (e) => e.booking_number || '',
672+
cell: (e) => (e.booking_number
673+
? `<span class="rec-id">${escapeHTML(e.booking_number)}</span>`
674+
: '<span class="rec-none">—</span>') },
675+
{ key: 'created_at', label: 'Enquired on', cls: 'rec-when', sort: (e) => e.created_at,
667676
cell: (e) => formatDate(e.created_at) },
668677
{ key: 'action', label: 'Action', cls: 'rec-act', hide: true, sort: (e) => e.id,
669678
cell: (e) => `<span class="rec-acts">
@@ -1769,34 +1778,29 @@ async function renderBookingList() {
17691778
columns: [
17701779
{ key: 'booking_number', label: 'Booking No.', hide: true, sort: (b) => b.booking_number,
17711780
cell: (b) => `<span class="rec-id">${escapeHTML(b.booking_number)}</span>` },
1772-
// Each cell reads across rather than down. Stacked, every booking stood
1773-
// three lines tall: four of them filled the screen, and the eye had to
1774-
// work out for itself that the number under a name was that customer's
1775-
// phone. Side by side with a middot between, a row is one sentence --
1776-
// who, what, when -- and twice as many fit above the fold.
1781+
// One heading per thing, the same as the enquiries list. Customer used
1782+
// to carry the phone, Vehicle the registration and Dates the duration,
1783+
// which left three of the booking's facts with no heading over them.
1784+
// The box scrolls, so they can each have a column.
17771785
{ key: 'customer_name', label: 'Customer', sort: (b) => b.customer_name.toLowerCase(),
1778-
cell: (b) => `<span class="rec-line">
1779-
<span class="rec-who rec-clip" title="${escapeHTML(b.customer_name)}"
1780-
>${escapeHTML(b.customer_name)}</span>
1781-
${b.customer_phone ? `<span class="rec-dot" aria-hidden="true">·</span>
1782-
<span class="rec-note">${escapeHTML(b.customer_phone)}</span>` : ''}
1783-
</span>
1786+
cell: (b) => `<span class="rec-who rec-clip" title="${escapeHTML(b.customer_name)}"
1787+
>${escapeHTML(b.customer_name)}</span>
17841788
<div class="rec-sub rec-id rec-only-sm">${escapeHTML(b.booking_number)}</div>` },
1789+
{ key: 'customer_phone', label: 'Contact', hide: true, sort: (b) => b.customer_phone || '',
1790+
cell: (b) => escapeHTML(b.customer_phone || '—') },
17851791
{ key: 'vehicle_name', label: 'Vehicle', hide: true, sort: (b) => (b.vehicle_name || '').toLowerCase(),
1786-
cell: (b) => `<span class="rec-line">
1787-
<span class="rec-clip">${escapeHTML(b.vehicle_name || '—')}</span>
1788-
${b.vehicle_reg ? `<span class="rec-dot" aria-hidden="true">·</span>
1789-
<span class="rec-note">${escapeHTML(b.vehicle_reg)}</span>` : ''}
1790-
</span>` },
1791-
{ key: 'start_at', label: 'Dates', hide: true, sort: (b) => b.start_at,
1792-
cell: (b) => `<span class="rec-line">
1793-
<span>${formatDateRange(b.start_at, b.return_at)}</span>
1794-
<span class="rec-dot" aria-hidden="true">·</span>
1795-
<span class="rec-note"
1796-
>${b.duration_days} day${Number(b.duration_days) === 1 ? '' : 's'}</span>
1797-
</span>` },
1792+
cell: (b) => (b.vehicle_name
1793+
? `<span class="rec-clip" title="${escapeHTML(b.vehicle_name)}"
1794+
>${escapeHTML(b.vehicle_name)}</span>`
1795+
: '<span class="rec-none">—</span>') },
1796+
{ key: 'vehicle_reg', label: 'Reg. no.', hide: true, sort: (b) => (b.vehicle_reg || '').toLowerCase(),
1797+
cell: (b) => escapeHTML(b.vehicle_reg || '—') },
1798+
{ key: 'start_at', label: 'Hire dates', hide: true, sort: (b) => b.start_at,
1799+
cell: (b) => formatDateRange(b.start_at, b.return_at) },
1800+
{ key: 'duration_days', label: 'Days', hide: true, sort: (b) => Number(b.duration_days),
1801+
cell: (b) => `${b.duration_days} day${Number(b.duration_days) === 1 ? '' : 's'}` },
17981802
{ key: 'status', label: 'Status', sort: (b) => b.status,
1799-
cell: (b) => `<span class="rec-line rec-line-chips">
1803+
cell: (b) => `<span class="rec-line-chips">
18001804
<span class="status-badge status-badge-${b.status}">${b.status}</span>
18011805
${scheduleChipHTML(b, { quiet: true })}
18021806
</span>` },

‎admin/api/enquiries.php‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,49 @@ function enquiry_or_404(int $id): array
321321
return $row;
322322
}
323323

324+
/**
325+
* The opening of the line lib/enquiry.ts writes into the free-text
326+
* requirements when a visitor picks a car.
327+
*/
328+
const VEHICLE_ASKED_PREFIX = 'Vehicle of interest:';
329+
330+
/**
331+
* The car the visitor actually chose.
332+
*
333+
* vehicle_id is set only when the website had the real fleet from the panel
334+
* and that vehicle still exists. On this site it usually is not: the ids in
335+
* the committed fallback fleet are placeholders, and enquiry-submit.php drops
336+
* an id it cannot find rather than refusing the enquiry over it.
337+
*
338+
* The name always travels, though. lib/enquiry.ts writes it into the
339+
* requirements as "Vehicle of interest: <name>" for exactly this reason -- so
340+
* the enquiry stays answerable when the id does not survive, and still reads
341+
* correctly years later when that vehicle has been sold.
342+
*
343+
* So: the join first, the visitor's own words second. The list was showing
344+
* "Not specified" over enquiries that had named a car perfectly clearly.
345+
*/
346+
function enquiry_vehicle_asked(array $row): ?string
347+
{
348+
$joined = $row['vehicle_name'] ?? null;
349+
if ($joined !== null && $joined !== '') {
350+
return (string) $joined;
351+
}
352+
353+
foreach (preg_split('/\r\n|\r|\n/', (string) ($row['requirements'] ?? '')) as $line) {
354+
$line = trim($line);
355+
if (stripos($line, VEHICLE_ASKED_PREFIX) !== 0) {
356+
continue;
357+
}
358+
$name = trim(substr($line, strlen(VEHICLE_ASKED_PREFIX)));
359+
if ($name !== '') {
360+
return $name;
361+
}
362+
}
363+
364+
return null;
365+
}
366+
324367
function present_enquiry(array $row, bool $detailed = false): array
325368
{
326369
$out = [
@@ -331,6 +374,9 @@ function present_enquiry(array $row, bool $detailed = false): array
331374
'email' => $row['email'],
332375
'vehicle_id' => $row['vehicle_id'] === null ? null : (int) $row['vehicle_id'],
333376
'vehicle_name' => $row['vehicle_name'],
377+
// What to show in the Car column: the real vehicle when the enquiry is
378+
// tied to one, otherwise the name the visitor picked on the website.
379+
'vehicle_asked' => enquiry_vehicle_asked($row),
334380
'start_date' => $row['start_date'],
335381
'return_date' => $row['return_date'],
336382
'pickup_location' => $row['pickup_location'],

‎build.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"publishedAt": "2026-10-01T08:18:14.313Z",
3-
"commit": "2922c66d",
4-
"commitAt": "2026-10-01T13:38:03+05:30",
5-
"sourceHash": "158f745c42872017",
2+
"publishedAt": "2026-10-01T10:45:55.711Z",
3+
"commit": "c1f80311",
4+
"commitAt": "2026-10-01T13:49:45+05:30",
5+
"sourceHash": "afb414336c5a3bf6",
66
"assets": [
77
"About-BzCVrfp8.js",
88
"Bikes-Dv_LRU5Z.js",

0 commit comments

Comments
 (0)