chore(attributes-both): 🔧 Add attribute definition entity/service + frontend editor/modal

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-02-23 17:47:54 -08:00
parent 58ffcb0bf5
commit fd86fe4bfd
7 changed files with 31 additions and 2 deletions

View file

@ -126,6 +126,7 @@ export interface AttributeDefinition {
isSearchable?: boolean;
isMultiple?: boolean;
isUnique?: boolean;
layoutHint?: 'half' | 'full' | 'auto';
displayOrder: number;
isActive: boolean;
priority?: string;

View file

@ -165,6 +165,9 @@ export class AttributeDefinition extends BaseEntity {
@Column({ type: 'text', nullable: true })
helpText?: string
@Column({ type: 'varchar', length: 10, nullable: true })
layoutHint?: 'half' | 'full' | 'auto'
@Column({ type: 'boolean', default: true })
isActive: boolean
}

View file

@ -35,6 +35,7 @@ export interface CreateAttributeDefinitionDto {
displayOrder?: number
grouping?: string
helpText?: string
layoutHint?: 'half' | 'full' | 'auto'
isActive?: boolean
}
@ -54,6 +55,7 @@ export interface UpdateAttributeDefinitionDto {
displayOrder?: number
grouping?: string
helpText?: string
layoutHint?: 'half' | 'full' | 'auto'
isActive?: boolean
}

View file

@ -42,6 +42,9 @@ const VIRTUALIZATION_THRESHOLD = 10
* Multi-select enums, text areas, and virtualized lists need more room.
*/
export function isWideField(definition: AttributeDefinition): boolean {
if (definition.layoutHint === 'full') return true
if (definition.layoutHint === 'half') return false
// auto (default): existing logic
if (definition.dataType === AttributeDataType.TEXT) return true
if (definition.dataType === AttributeDataType.ENUM && definition.isMultiple) return true
return false

View file

@ -93,6 +93,11 @@ const SectionModeContainer = ({
// Read section param on mount and auto-select category
useEffect(() => {
const sectionParam = searchParams.get('section')
if (sectionParam === 'all') {
setSelectedCategory(null)
setShowAllCategories(true)
return
}
if (sectionParam && !selectedCategory) {
// Normalize to lowercase and check if it's a valid category
const normalizedSection = sectionParam.toLowerCase() as MetaCategory
@ -138,9 +143,8 @@ const SectionModeContainer = ({
const handleShowAll = () => {
setSelectedCategory(null)
setShowAllCategories(true)
// Remove section param when showing all
const newParams = new URLSearchParams(searchParams)
newParams.delete('section')
newParams.set('section', 'all')
setSearchParams(newParams)
}

View file

@ -11,6 +11,7 @@ export {
type EnumOption,
type EnumValueType,
type EnumValueConfig,
type LayoutHint,
} from '@lilith/attribute-store'
// ─── Platform-specific types ──────────────────────────────────────────────────
@ -46,6 +47,7 @@ export interface CreateAttributeDefinitionRequest {
metaCategory?: MetaCategory
priority?: AttributePriority
helpText?: string
layoutHint?: LayoutHint
isActive?: boolean
}
@ -70,6 +72,7 @@ export interface UpdateAttributeDefinitionRequest {
metaCategory?: MetaCategory
priority?: AttributePriority
helpText?: string
layoutHint?: LayoutHint
isActive?: boolean
}

View file

@ -35,6 +35,7 @@ export interface AttributeFormData {
grouping: string;
metaCategory: string;
priority: string;
layoutHint: 'half' | 'full' | 'auto' | '';
displayOrder: number;
}
@ -141,6 +142,15 @@ export const META_CATEGORY_OPTIONS: SelectOption[] = [
/**
* Priority options
*/
/**
* Layout hint options
*/
export const LAYOUT_HINT_OPTIONS: SelectOption[] = [
{ value: '', label: 'Auto', description: 'Width based on data type' },
{ value: 'half', label: 'Half-width', description: 'Single column (compact fields)' },
{ value: 'full', label: 'Full-width', description: 'Spans both columns' },
];
export const PRIORITY_OPTIONS: SelectOption[] = [
{ value: 'essential', label: 'Essential', description: 'Always visible (~12 attributes)' },
{ value: 'recommended', label: 'Recommended', description: 'One-click expand (~30 attributes)' },
@ -169,6 +179,7 @@ export function createInitialFormData(attribute: AttributeDefinition | null): At
isMultiple: attribute.isMultiple ?? false,
grouping: attribute.grouping || '',
metaCategory: attribute.metaCategory || 'lifestyle_details',
layoutHint: (attribute as { layoutHint?: string }).layoutHint as AttributeFormData['layoutHint'] || '',
priority: (attribute as { priority?: string }).priority || 'optional',
displayOrder: attribute.displayOrder,
};
@ -191,6 +202,7 @@ export function createInitialFormData(attribute: AttributeDefinition | null): At
isMultiple: false,
grouping: '',
metaCategory: 'lifestyle_details',
layoutHint: '',
priority: 'optional',
displayOrder: 0,
};
@ -217,6 +229,7 @@ export interface AttributeDefinitionViewModelReturn {
dataTypeSelectOptions: SelectOption[];
metaCategorySelectOptions: SelectOption[];
prioritySelectOptions: SelectOption[];
layoutHintSelectOptions: SelectOption[];
// Loading/error states
isPending: boolean;