MM-62172: CPA/User Properties - System Console (#29672)

* system properties - user properties

* update

* fix processing

* refinements, fix cancel changes, fix type inclusion when saving/creating, i18n

* fix:
- property name value trimmed
- name unique validation
- name required validation
- name max-length
- user properties section titlecase
- new property default incremented name
- new property auto-focus/select
- max fields count
- table design/styling

* add useUserPropertyFields test
Этот коммит содержится в:
Caleb Roseland
2025-01-14 11:42:46 -06:00
коммит произвёл GitHub
родитель e400e67732
Коммит 0085826203
23 изменённых файлов: 1407 добавлений и 22 удалений

Просмотреть файл

@@ -31,6 +31,8 @@ export type StatusOK = {
status: 'OK';
};
export const isStatusOK = (x: StatusOK | Record<string, unknown>): x is StatusOK => (x as StatusOK)?.status === 'OK';
export type FetchPaginatedThreadOptions = {
fetchThreads?: boolean;
collapsedThreads?: boolean;

Просмотреть файл

@@ -13,6 +13,13 @@ export type RelationOneToManyUnique<E1 extends {id: string}, E2 extends {id: str
export type IDMappedObjects<E extends {id: string}> = RelationOneToOne<E, E>;
export type IDMappedCollection<T extends {id: string}> = {
data: IDMappedObjects<T>;
order: Array<T['id']>;
errors?: RelationOneToOne<T, Error>;
warnings?: RelationOneToOne<T, {[Key in keyof T]?: string}>;
};
export type DeepPartial<T> = {
// For each field of T, make it optional and...
@@ -44,6 +51,10 @@ Pick<T, Exclude<keyof T, Keys>> & {[K in Keys]-?: Required<Pick<T, K>> & Partial
export type Intersection<T1, T2> =
Omit<Omit<T1&T2, keyof(Omit<T1, keyof(T2)>)>, keyof(Omit<T2, keyof(T1)>)>;
/** https://stackoverflow.com/a/66605669 */
type Only<T, U> = {[P in keyof T]: T[P]} & {[P in keyof U]?: never};
export type Either<T, U> = Only<T, U> | Only<U, T>;
export type PartialExcept<T extends Record<string, unknown>, TKeysNotPartial extends keyof T> = Partial<T> & Pick<T, TKeysNotPartial>;
export function isArrayOf<T>(v: unknown, check: (e: unknown) => boolean): v is T[] {
@@ -73,3 +84,30 @@ export function isRecordOf<T>(v: unknown, check: (e: unknown) => boolean): v is
return true;
}
export const collectionFromArray = <T extends {id: string}>(arr: T[] = []): IDMappedCollection<T> => {
return arr.reduce((current, item) => {
current.data = {...current.data, [item.id]: item};
current.order.push(item.id);
return current;
}, {data: {} as IDMappedObjects<T>, order: []} as IDMappedCollection<T>);
};
export const collectionToArray = <T extends {id: string}>({data, order}: IDMappedCollection<T>): T[] => {
return order.map((id) => data[id]);
};
export const collectionReplaceItem = <T extends {id: string}>(collection: IDMappedCollection<T>, item: T) => {
return {...collection, data: {...collection.data, [item.id]: item}};
};
export const collectionAddItem = <T extends {id: string}>(collection: IDMappedCollection<T>, item: T) => {
return {...collection, data: {...collection.data, [item.id]: item}, order: [...collection.order, item.id]};
};
export const collectionRemoveItem = <T extends {id: string}>(collection: IDMappedCollection<T>, item: T) => {
const data = {...collection.data};
Reflect.deleteProperty(data, item.id);
const order = collection.order.filter((id) => id !== item.id);
return {...collection, data, order};
};