Skip to content

Commit 9753d16

Browse files
fix(console): audit log From / To filters use the platform date picker (#26061)
Fixes #25875 Clause-②: no Metadata-protocol: none ## What changed Setup → Audit Log drew the browser's native `input type=date` for its From and To filters, which shows the operating system's `mm/dd/yyyy` face and picker whatever the console's language. Both filters now draw the platform date picker that a record form's `date` field draws (the objectui#12642 family): a text box in the display locale's format, a calendar popover, a clear button and today / tomorrow shortcuts. - `ui/apps/console/src/pages/system/AuditLogPage.tsx`: From and To render `getLazyFieldWidget('date')` (resolved once at module scope, as `ScreenView` does for `lookup`) inside a `Suspense` with a `Skeleton` fallback. Props: `id` (the existing label target), `value`, `onChange` (keeps `setPage(0)`), `field={{ type: 'date', name, label }}`. The `Input` import is dropped (no other use). - No direct `DatePickerField` import and no new subpath export: the picker stays in its own lazy chunk, reached through the existing public `getLazyFieldWidget`. - The filter state and the request do not change: the picker writes `YYYY-MM-DD`, and `''` on clear, as the native input did, so the `created_at` bounds are the same (`$gte` the From day, `$lte` the To day at `T23:59:59`). "Clear filters" resets both pickers through the same state. ## Tests `AuditLogPage.filterLabels-13224.test.tsx` flips its native-input pins: - From and To each still name exactly one control, and it is the picker's text box (`type=text`, inside the `date-picker` slot), never its calendar button; no `input type=date` is left on the page. - The live-filter case types `10/01/2026` (en-US display format) and blurs, then `10/20/2026` and presses Enter. The requests carry `{ created_at: { $gte: '2026-10-01' } }` and then `{ created_at: { $gte: '2026-10-01', $lte: '2026-10-20T23:59:59' } }`. ## Verification (head `63114661`, after merging `origin/main`) - `vitest run --project @object-ui/console` over the six `AuditLogPage.*.test.tsx`, `consolePagesLocale-secondPass-13460`, `consoleFaces.displayLocale-9909` and `orphanedPageComponentRefs-10520`: Test Files 9 passed (9), Tests 68 passed (68). - `turbo run type-check --filter=@object-ui/console` (with its `^build` closure): 71 successful, 71 total, exit 0. - eslint over the two changed files: 0 errors (2 warnings already on `main`, on lines this diff does not touch). - Ablation (fix committed first; the page file restored from `HEAD` and its blob hash compared): with `AuditLogPage.tsx` swapped for the base copy (2 native date inputs, 0 picker), the flipped suite goes red, 2 failed / 1 passed. Restored: hash matches `HEAD`, `git diff HEAD` empty. - `dispatch-gates --commands` derived 34 commands. All 34 exited 0, and `--ran` reconciles: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. `check-changeset-fixed` (its roster lives under `.changeset/`) also exited 0. `check-sdui-manifest` is green, so `scripts/sdui-manifest.record.json` is untouched. ## Acceptance notes - The audit log's English filter labels belong to objectui#13521. This PR changes the control only. - The picker's own strings (`fields.datePicker.*`) already ship in every locale pack. No locale file is touched. --- _Generated by [Claude Code](https://claude.ai/code/session_01SMps7wq7rBTk53e8KTSEup)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9a0af4e commit 9753d16

3 files changed

Lines changed: 74 additions & 11 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@object-ui/console': patch
3+
---
4+
5+
fix(console): the audit log's From and To filters use the platform date picker the record forms use
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
Setup → Audit Log drew the browser's native date input for its From and To filters, with the operating system's `mm/dd/yyyy` face and picker whatever the console's language. Both filters now draw the same date picker as a record form's `date` field: a text box in the display locale's format, a calendar popover, a clear button and today / tomorrow shortcuts. A typed day commits on blur or Enter. The filters send the same `created_at` bounds as before (`YYYY-MM-DD`, the To day through 23:59:59), and each filter label still names its text box.

‎ui/apps/console/src/pages/system/AuditLogPage.filterLabels-13224.test.tsx‎

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@
2626
* it is that filter's own control: the Action select's trigger, the Object
2727
* combobox, the Actor lookup's trigger, and the two date fields;
2828
* - the controls found by their labels are the live filters: the action, the
29-
* From date and the To date each still send the filter they always sent.
29+
* From date and the To date each still send the filter they always sent;
30+
* - From and To are the platform date picker the record forms draw
31+
* (objectstack#25875), not the browser's native date input: the label names
32+
* the picker's text box, never its calendar button, and a day typed in the
33+
* display locale's format commits on blur or Enter as `YYYY-MM-DD`.
3034
*/
3135

3236
import '@testing-library/jest-dom/vitest';
@@ -134,6 +138,18 @@ function renderPage(): void {
134138
render(<MemoryRouter><AuditLogPage /></MemoryRouter>);
135139
}
136140

141+
/**
142+
* Waits for the From and To date pickers to mount. They are the lazily loaded
143+
* `getLazyFieldWidget('date')` chunk behind a `Suspense` skeleton, and until
144+
* the chunk resolves no text box carries the label's id; the zero-delay ticks
145+
* of `settle()` do not cover a chunk load on a slow runner.
146+
*/
147+
async function datePickersMounted(): Promise<void> {
148+
for (const label of ['From', 'To']) {
149+
await waitFor(() => expect(screen.queryAllByLabelText(label, { exact: true })).toHaveLength(1), { timeout: 5000 });
150+
}
151+
}
152+
137153
/** The one control a visible filter label names; fails unless it names exactly one. */
138154
function controlLabelled(label: string): HTMLElement {
139155
const controls = screen.queryAllByLabelText(label, { exact: true });
@@ -145,6 +161,7 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
145161
it('Action, Object, Actor, From and To each name exactly one control, their own', async () => {
146162
renderPage();
147163
await settle();
164+
await datePickersMounted();
148165

149166
const action = controlLabelled('Action');
150167
const object = controlLabelled('Object');
@@ -160,9 +177,14 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
160177
expect(object).toHaveTextContent('Any object');
161178
// The Actor lookup's trigger button.
162179
expect(actor).toBe(screen.getByTestId('lookup-trigger-user_id'));
163-
// The two date fields.
164-
expect(from).toHaveAttribute('type', 'date');
165-
expect(to).toHaveAttribute('type', 'date');
180+
// The two date fields: the platform picker's text box, not the native
181+
// date input and not the picker's calendar button (objectstack#25875).
182+
for (const field of [from, to]) {
183+
expect(field.tagName).toBe('INPUT');
184+
expect(field).toHaveAttribute('type', 'text');
185+
expect(field.closest('[data-slot="date-picker"]'), 'date picker not rendered').not.toBeNull();
186+
}
187+
expect(document.querySelector('input[type="date"]')).toBeNull();
166188
expect(new Set([action, object, actor, from, to]).size).toBe(5);
167189

168190
// The accessible name assistive technology announces is the label's.
@@ -187,15 +209,22 @@ describe('AuditLogPage — each filter control is named by its visible label (ob
187209
it('the date fields found by their labels are the live From and To filters', async () => {
188210
renderPage();
189211
await settle();
212+
await datePickersMounted();
190213

214+
// Typed in the display locale's format (en-US here), committed on blur.
215+
const from = controlLabelled('From');
191216
await act(async () => {
192-
fireEvent.change(controlLabelled('From'), { target: { value: '2026-10-01' } });
217+
fireEvent.change(from, { target: { value: '10/01/2026' } });
218+
fireEvent.blur(from);
193219
});
194220
await settle();
195221
await waitFor(() => expect(lastFilter()).toEqual({ created_at: { $gte: '2026-10-01' } }));
196222

223+
// …and on Enter.
224+
const to = controlLabelled('To');
197225
await act(async () => {
198-
fireEvent.change(controlLabelled('To'), { target: { value: '2026-10-20' } });
226+
fireEvent.change(to, { target: { value: '10/20/2026' } });
227+
fireEvent.keyDown(to, { key: 'Enter' });
199228
});
200229
await settle();
201230
await waitFor(() =>

‎ui/apps/console/src/pages/system/AuditLogPage.tsx‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@
3434
* once FLS lands)
3535
*/
3636

37-
import { useCallback, useEffect, useId, useMemo, useState } from 'react';
37+
import { Suspense, useCallback, useEffect, useId, useMemo, useState } from 'react';
3838
import { Link, useParams } from 'react-router-dom';
3939
import { mergeFilterNodes, toFilterNodeSafely } from '@object-ui/core';
4040
import { useDisplayLocale, useObjectTranslation } from '@object-ui/i18n';
4141
import {
4242
Button,
4343
Card,
4444
CardContent,
45-
Input,
4645
Label,
4746
Badge,
4847
Table,
@@ -71,7 +70,7 @@ import {
7170
Combobox,
7271
type ComboboxOption,
7372
} from '@object-ui/components';
74-
import { LookupField, objectRefOptionLabel } from '@object-ui/fields';
73+
import { LookupField, getLazyFieldWidget, objectRefOptionLabel } from '@object-ui/fields';
7574
import { ConsolePageHeader, useAdapter, useMetadata } from '@object-ui/app-shell';
7675
import { Search, X, AlertCircle, ScrollText, ExternalLink } from 'lucide-react';
7776
import { actionBadgeVariant, actionFilterOptionsOf } from './auditLogActions';
@@ -105,6 +104,15 @@ interface AuditRow {
105104

106105
const PAGE_SIZE = 50;
107106

107+
/**
108+
* The platform date picker a record form's `date` field draws (objectui#12642):
109+
* a text box in the display locale's format with a calendar popover, through
110+
* the shared lazy widget registry, so it stays in its own chunk. It writes
111+
* `YYYY-MM-DD` and `''` on clear, the values the From / To filters send.
112+
* Resolved once at module scope, not in render (`react-hooks/static-components`).
113+
*/
114+
const DateFilterWidget = getLazyFieldWidget('date');
115+
108116
/** The object this page browses. */
109117
const AUDIT_OBJECT = 'sys_audit_log';
110118

@@ -562,11 +570,27 @@ export function AuditLogPage() {
562570
</div>
563571
<div className="space-y-1.5">
564572
<Label htmlFor={fromDateId} className="text-xs">{t('console.system.auditLog.from', { defaultValue: 'From' })}</Label>
565-
<Input id={fromDateId} type="date" value={fromDate} onChange={(e) => { setFromDate(e.target.value); setPage(0); }} className="h-9" />
573+
{/* The record form's date picker. Its `id` reaches the text box, the
574+
control the label names; typed text commits on blur or Enter. */}
575+
<Suspense fallback={<Skeleton className="h-9 w-full" />}>
576+
<DateFilterWidget
577+
id={fromDateId}
578+
value={fromDate}
579+
onChange={(v: unknown) => { setFromDate(v == null ? '' : String(v)); setPage(0); }}
580+
field={{ type: 'date', name: 'created_at_from', label: t('console.system.auditLog.from', { defaultValue: 'From' }) }}
581+
/>
582+
</Suspense>
566583
</div>
567584
<div className="space-y-1.5">
568585
<Label htmlFor={toDateId} className="text-xs">{t('console.system.auditLog.to', { defaultValue: 'To' })}</Label>
569-
<Input id={toDateId} type="date" value={toDate} onChange={(e) => { setToDate(e.target.value); setPage(0); }} className="h-9" />
586+
<Suspense fallback={<Skeleton className="h-9 w-full" />}>
587+
<DateFilterWidget
588+
id={toDateId}
589+
value={toDate}
590+
onChange={(v: unknown) => { setToDate(v == null ? '' : String(v)); setPage(0); }}
591+
field={{ type: 'date', name: 'created_at_to', label: t('console.system.auditLog.to', { defaultValue: 'To' }) }}
592+
/>
593+
</Suspense>
570594
</div>
571595
{activeFilterCount > 0 && (
572596
<div className="md:col-span-5 flex justify-end">

0 commit comments

Comments
 (0)