diff --git a/ordinis-admin-ui/nginx.conf b/ordinis-admin-ui/nginx.conf index 9fcaaed..506cc82 100644 --- a/ordinis-admin-ui/nginx.conf +++ b/ordinis-admin-ui/nginx.conf @@ -15,9 +15,10 @@ server { root /usr/share/nginx/html; index index.html; - # /api/v1/dictionaries/{name} (без /records) — admin endpoint, отдаёт schema_json. - # Только writer его реализует. Regex-location имеет приоритет над префиксом ниже. - location ~ ^/api/v1/dictionaries/[^/]+$ { + # /api/v1/dictionaries/* — все admin endpoints (schema, raw record, mutations) идут + # на writer. Read-api держит /api/v1/{name}/records/* (другой URL pattern, flatten i18n). + # Regex-location имеет приоритет над префиксом ниже. + location ~ ^/api/v1/dictionaries/ { set $backend "ordinis-app.${ORDINIS_NAMESPACE}.svc.${CLUSTER_DOMAIN}"; proxy_pass http://$backend$request_uri; proxy_http_version 1.1; diff --git a/ordinis-admin-ui/src/api/queries.ts b/ordinis-admin-ui/src/api/queries.ts index 565d6be..930b79c 100644 --- a/ordinis-admin-ui/src/api/queries.ts +++ b/ordinis-admin-ui/src/api/queries.ts @@ -4,6 +4,7 @@ import { type DictionaryDefinition, type DictionaryDetail, type FlattenedRecord, + type RecordResponse, } from './client' export const dictionariesQuery = queryOptions({ @@ -35,7 +36,26 @@ export const recordsQuery = (dictionaryName: string, scopeCsv: string) => }, }) +export const recordRawQuery = (dictionaryName: string, businessKey: string) => + queryOptions({ + queryKey: ['record-raw', dictionaryName, businessKey] as const, + queryFn: async (): Promise => { + const { data } = await apiClient.get( + `/dictionaries/${dictionaryName}/records/${encodeURIComponent(businessKey)}`, + ) + return data + }, + }) + export const useDictionaries = () => useQuery(dictionariesQuery) export const useDictionaryDetail = (name: string) => useQuery(dictionaryDetailQuery(name)) export const useRecords = (dictionaryName: string, scopeCsv: string) => useQuery(recordsQuery(dictionaryName, scopeCsv)) +export const useRecordRaw = ( + dictionaryName: string, + businessKey: string | undefined, +) => + useQuery({ + ...recordRawQuery(dictionaryName, businessKey ?? ''), + enabled: Boolean(businessKey), + }) diff --git a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx index d2d354f..5cfef9a 100644 --- a/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx +++ b/ordinis-admin-ui/src/components/form/SchemaDrivenForm.tsx @@ -8,6 +8,7 @@ import { Badge, Button, Checkbox, + DatePicker, FieldError, FieldHint, FieldLabel, @@ -48,6 +49,22 @@ const isLocalized = (s: JsonSchema): boolean => Boolean(s['x-localized']) const humanize = (key: string): string => key.replace(/[_-]+/g, ' ').replace(/\b\w/g, (ch) => ch.toUpperCase()) +const parseFormDate = (s: unknown): Date | null => { + if (typeof s !== 'string' || !s) return null + const d = new Date(s) + if (Number.isNaN(d.getTime())) return null + if (d.getFullYear() >= 9999) return null + return d +} + +const formatIsoDate = (d: Date): string => { + const pad = (n: number) => String(n).padStart(2, '0') + return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +} + +const isDateFormat = (s: JsonSchema): boolean => + s.type === 'string' && (s.format === 'date' || s.format === 'date-time') + type TabId = 'identity' | 'description' | 'extra' const bucketProperties = ( @@ -154,20 +171,33 @@ export const SchemaDrivenForm = ({ {...register('businessKey', { required: true, minLength: 1 })} /> - ( + field.onChange(d ? formatIsoDate(d) : '')} + /> + )} /> - ( + field.onChange(d ? formatIsoDate(d) : '')} + /> + )} /> {buckets.identity.map((key) => ( ( + field.onChange(d ? formatIsoDate(d) : undefined)} + /> + )} + /> + ) + } + if (schema.type === 'integer' || schema.type === 'number') { return ( ( + + )} + /> + ) + } + + if (schema.type === 'array' && schema.items?.type === 'object') { + return ( + ( + + )} + /> + ) + } + if (schema.type === 'array' && schema.items?.type === 'string') { return ( void +} + +const JsonField = ({ label, required, hint, error, value, onChange }: JsonFieldProps) => { + const [text, setText] = useState(() => + value === undefined || value === null ? '' : JSON.stringify(value, null, 2), + ) + const [parseError, setParseError] = useState(null) + + return ( +