diff --git a/ordinis-admin-ui/package.json b/ordinis-admin-ui/package.json index a86e4bf..c24194c 100644 --- a/ordinis-admin-ui/package.json +++ b/ordinis-admin-ui/package.json @@ -31,6 +31,7 @@ "axios": "^1.7.9", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "cmdk": "^1.1.1", "d3-force": "^3.0.0", "d3-geo": "^3.1.1", "i18next": "^26.0.3", @@ -39,6 +40,7 @@ "lucide-react": "^1.14.0", "oidc-client-ts": "^3.5.0", "react": "^19.0.0", + "react-day-picker": "^10.0.0", "react-dom": "^19.0.0", "react-force-graph-3d": "^1.29.1", "react-hook-form": "^7.54.2", diff --git a/ordinis-admin-ui/pnpm-lock.yaml b/ordinis-admin-ui/pnpm-lock.yaml index 8fe3b88..2bb8b3a 100644 --- a/ordinis-admin-ui/pnpm-lock.yaml +++ b/ordinis-admin-ui/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + cmdk: + specifier: ^1.1.1 + version: 1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) d3-force: specifier: ^3.0.0 version: 3.0.0 @@ -92,6 +95,9 @@ importers: react: specifier: ^19.0.0 version: 19.2.5 + react-day-picker: + specifier: ^10.0.0 + version: 10.0.0(react@19.2.5) react-dom: specifier: ^19.0.0 version: 19.2.5(react@19.2.5) @@ -378,6 +384,9 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} + '@date-fns/tz@1.4.1': + resolution: {integrity: sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==} + '@dimforge/rapier3d-compat@0.12.0': resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} @@ -1959,6 +1968,12 @@ packages: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -2121,6 +2136,9 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -3246,6 +3264,12 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-day-picker@10.0.0: + resolution: {integrity: sha512-lrEXo5wFPsq5LTcayelM3BPueD00v7zbdipAY+EIdPcseVykYwkOWx4Ujn/EtbBvpnp8ZPUHol17HXH6kVbZoA==} + engines: {node: '>=18'} + peerDependencies: + react: '>=16.8.0' + react-dom@19.2.5: resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: @@ -4149,6 +4173,8 @@ snapshots: '@csstools/css-tokenizer@4.0.0': {} + '@date-fns/tz@1.4.1': {} + '@dimforge/rapier3d-compat@0.12.0': {} '@esbuild/aix-ppc64@0.21.5': @@ -5597,6 +5623,18 @@ snapshots: clsx@2.1.1: {} + cmdk@1.1.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.5) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + color-convert@1.9.3: dependencies: color-name: 1.1.3 @@ -5758,6 +5796,8 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + date-fns@4.1.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -6731,6 +6771,12 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-day-picker@10.0.0(react@19.2.5): + dependencies: + '@date-fns/tz': 1.4.1 + date-fns: 4.1.0 + react: 19.2.5 + react-dom@19.2.5(react@19.2.5): dependencies: react: 19.2.5 diff --git a/ordinis-admin-ui/src/i18n.ts b/ordinis-admin-ui/src/i18n.ts index 8fb8211..6a33b6c 100644 --- a/ordinis-admin-ui/src/i18n.ts +++ b/ordinis-admin-ui/src/i18n.ts @@ -161,6 +161,7 @@ i18n 'webhooks.field.dictionaryFilterHint': 'Через запятую: spacecraft, satellite_type. Пусто = все', 'webhooks.field.eventTypeFilter': 'Типы событий', 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Пусто = все события.', + 'webhooks.field.eventTypeSearch': 'Поиск типа события', 'webhooks.field.description': 'Описание', 'webhooks.field.createdBy': 'Создатель', 'webhooks.error.nameUrlRequired': 'Имя и URL обязательны', @@ -234,6 +235,7 @@ i18n 'audit.filter.businessKey': 'Бизнес-ключ', 'audit.filter.from': 'С', 'audit.filter.to': 'По', + 'audit.filter.range': 'Период', 'audit.filter.reset': 'Сбросить', 'audit.filter.apply': 'Применить', 'audit.filter.active': 'Активные фильтры', @@ -846,6 +848,7 @@ i18n 'webhooks.field.dictionaryFilterHint': 'Comma-separated: spacecraft, satellite_type. Empty = all', 'webhooks.field.eventTypeFilter': 'Event types', 'webhooks.field.eventTypeFilterHint': 'Record*, Definition*, SchemaDraft*. Empty = all events.', + 'webhooks.field.eventTypeSearch': 'Search event type', 'webhooks.field.description': 'Description', 'webhooks.field.createdBy': 'Created by', 'webhooks.error.nameUrlRequired': 'Name and URL are required', @@ -917,6 +920,7 @@ i18n 'audit.filter.businessKey': 'Business key', 'audit.filter.from': 'From', 'audit.filter.to': 'To', + 'audit.filter.range': 'Date range', 'audit.filter.reset': 'Reset', 'audit.filter.apply': 'Apply', 'audit.filter.active': 'Active filters', diff --git a/ordinis-admin-ui/src/routes/audit.tsx b/ordinis-admin-ui/src/routes/audit.tsx index 87d8dfc..d1512e0 100644 --- a/ordinis-admin-ui/src/routes/audit.tsx +++ b/ordinis-admin-ui/src/routes/audit.tsx @@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next' import { Badge, Button, - DatePicker, + DateRangePicker, EmptyState, IconButton, LoadingBlock, @@ -380,34 +380,29 @@ function FilterPanel({ onChange={(e) => set('businessKey', e.target.value || undefined)} onKeyDown={handleEnter} /> - - set( - 'from', - d - ? `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String( - d.getDate(), - ).padStart(2, '0')}T00:00:00${localTzOffset(d)}` - : undefined, - ) - } - /> - - set( - 'to', - d - ? `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String( - d.getDate(), - ).padStart(2, '0')}T23:59:59${localTzOffset(d)}` - : undefined, - ) - } - /> + {/* DateRangePicker заменяет пару отдельных DatePicker'ов — один + * popover с двумя месяцами, выбор start/end в одном UI. ISO sтроки + * c local TZ offset формируются здесь, чтобы backend получал + * правильный bounds (T00:00:00 для from, T23:59:59 для to). */} +
+ { + const fromIso = from + ? `${from.getFullYear()}-${String(from.getMonth() + 1).padStart(2, '0')}-${String( + from.getDate(), + ).padStart(2, '0')}T00:00:00${localTzOffset(from)}` + : undefined + const toIso = to + ? `${to.getFullYear()}-${String(to.getMonth() + 1).padStart(2, '0')}-${String( + to.getDate(), + ).padStart(2, '0')}T23:59:59${localTzOffset(to)}` + : undefined + onChange({ ...filters, from: fromIso, to: toIso }) + }} + /> +