chore(attributes): 🔧 Add profile attribute editor UI with search/filter navigation and backend value processing enhancements
This commit is contained in:
parent
ff5bb73227
commit
10eed343db
15 changed files with 121 additions and 85 deletions
|
|
@ -8,13 +8,14 @@
|
|||
* Or: pnpm seed:image-semantics
|
||||
*/
|
||||
|
||||
import { DataSource } from 'typeorm';
|
||||
import { config } from 'dotenv';
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { categoryImageSemanticsSeeds } from './category-image-semantics.seed';
|
||||
import { filterSemanticOverrideSeeds } from './filter-semantic-overrides.seed';
|
||||
|
||||
import { CategoryImageSemantics } from '@/entities/category-image-semantics.entity.js';
|
||||
import { FilterSemanticOverride } from '@/entities/filter-semantic-override.entity.js';
|
||||
import { categoryImageSemanticsSeeds } from './category-image-semantics.seed';
|
||||
import { filterSemanticOverrideSeeds } from './filter-semantic-overrides.seed';
|
||||
|
||||
config({ path: '.env.local' });
|
||||
config();
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
// Seed runner for attribute definitions
|
||||
// Run with: npx ts-node --esm src/seeds/run-seed.ts
|
||||
|
||||
import { DataSource } from 'typeorm'
|
||||
import { config } from 'dotenv'
|
||||
import { DataSource } from 'typeorm'
|
||||
|
||||
import { AttributeDefinition, EntityType, AttributeDataType } from '@/entities/attribute-definition.entity.js'
|
||||
import { attributeDefinitionSeeds, getSearchableCount, getAttributesByGrouping } from './attribute-definitions.seed'
|
||||
|
||||
import type { EntityType, AttributeDataType } from '@/entities/attribute-definition.entity.js';
|
||||
|
||||
import { AttributeDefinition } from '@/entities/attribute-definition.entity.js'
|
||||
|
||||
|
||||
config({ path: '.env.local' })
|
||||
config()
|
||||
|
||||
|
|
@ -36,7 +40,7 @@ async function runSeed() {
|
|||
|
||||
let created = 0
|
||||
let updated = 0
|
||||
let skipped = 0
|
||||
const skipped = 0
|
||||
|
||||
for (const seed of attributeDefinitionSeeds) {
|
||||
const existing = await repository.findOne({ where: { code: seed.code } })
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import { Injectable, BadRequestException } from '@nestjs/common'
|
|||
import { InjectRepository } from '@nestjs/typeorm'
|
||||
import { Repository } from 'typeorm'
|
||||
|
||||
import { AttributeDefinitionService } from './attribute-definition.service'
|
||||
|
||||
import { EntityType, AttributeDataType } from '@/entities/attribute-definition.entity.js'
|
||||
import { AttributeValue } from '@/entities/attribute-value.entity.js'
|
||||
|
||||
import { AttributeDefinitionService } from './attribute-definition.service'
|
||||
|
||||
@Injectable()
|
||||
export class AttributeValueService {
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ export async function fetchAttributeDefinitions(
|
|||
filters?: AttributeDefinitionFilters
|
||||
): Promise<AttributeDefinition[]> {
|
||||
const params = new URLSearchParams()
|
||||
if (entityType) params.set('entityType', entityType)
|
||||
if (filters?.category) params.set('category', filters.category)
|
||||
if (filters?.isActive !== undefined) params.set('isActive', String(filters.isActive))
|
||||
if (entityType) {params.set('entityType', entityType)}
|
||||
if (filters?.category) {params.set('category', filters.category)}
|
||||
if (filters?.isActive !== undefined) {params.set('isActive', String(filters.isActive))}
|
||||
|
||||
const response = await fetch(`${API_BASE}/attribute-definitions?${params}`)
|
||||
if (!response.ok) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
|
||||
import styled from '@lilith/ui-styled-components';
|
||||
import type { AttributeDefinition, EntityType } from '../types';
|
||||
import { useAttributeDefinitions } from '../hooks/useAttributeDefinitions';
|
||||
import { useAttributeCategories } from '../hooks/useMeta';
|
||||
|
||||
import type { AttributeDefinition, EntityType } from '@/types';
|
||||
|
||||
import { useAttributeDefinitions } from '@/hooks/useAttributeDefinitions';
|
||||
import { useAttributeCategories } from '@/hooks/useMeta';
|
||||
|
||||
|
||||
interface AttributeFilterValue {
|
||||
code: string;
|
||||
|
|
@ -47,10 +51,10 @@ export const AttributeFilter: React.FC<AttributeFilterProps> = ({
|
|||
useAttributeCategories(entityType);
|
||||
|
||||
const searchableDefinitions = useMemo(() => {
|
||||
if (!definitions) return [];
|
||||
if (!definitions) {return [];}
|
||||
return definitions.filter(def => {
|
||||
if (!def.isActive) return false;
|
||||
if (searchableOnly && !def.isSearchable) return false;
|
||||
if (!def.isActive) {return false;}
|
||||
if (searchableOnly && !def.isSearchable) {return false;}
|
||||
return true;
|
||||
});
|
||||
}, [definitions, searchableOnly]);
|
||||
|
|
@ -59,7 +63,7 @@ export const AttributeFilter: React.FC<AttributeFilterProps> = ({
|
|||
const groups: Record<string, AttributeDefinition[]> = {};
|
||||
searchableDefinitions.forEach(def => {
|
||||
const group = def.grouping || 'Other';
|
||||
if (!groups[group]) groups[group] = [];
|
||||
if (!groups[group]) {groups[group] = [];}
|
||||
groups[group].push(def);
|
||||
});
|
||||
return groups;
|
||||
|
|
@ -71,10 +75,10 @@ export const AttributeFilter: React.FC<AttributeFilterProps> = ({
|
|||
}, [searchableDefinitions, activeFilters]);
|
||||
|
||||
const handleAddFilter = useCallback((code: string) => {
|
||||
if (activeFilters.length >= maxFilters) return;
|
||||
if (activeFilters.length >= maxFilters) {return;}
|
||||
|
||||
const def = searchableDefinitions.find(d => d.code === code);
|
||||
if (!def) return;
|
||||
if (!def) {return;}
|
||||
|
||||
const defaultValue = getDefaultFilterValue(def);
|
||||
const newFilter: AttributeFilterValue = {
|
||||
|
|
@ -128,7 +132,7 @@ export const AttributeFilter: React.FC<AttributeFilterProps> = ({
|
|||
<ActiveFiltersSection>
|
||||
{activeFilters.map(filter => {
|
||||
const def = searchableDefinitions.find(d => d.code === filter.code);
|
||||
if (!def) return null;
|
||||
if (!def) {return null;}
|
||||
|
||||
return (
|
||||
<ActiveFilterChip key={filter.code}>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import styled from '@lilith/ui-styled-components';
|
||||
import type { AttributeDefinition, EntityType } from '../types';
|
||||
import { useAttributeDefinitions } from '../hooks/useAttributeDefinitions';
|
||||
|
||||
import type { AttributeFilterValue } from './AttributeFilter';
|
||||
import type { AttributeDefinition, EntityType } from '@/types';
|
||||
|
||||
import { useAttributeDefinitions } from '@/hooks/useAttributeDefinitions';
|
||||
|
||||
|
||||
interface AttributeSearchPillsProps {
|
||||
entityType: EntityType;
|
||||
|
|
@ -41,12 +45,10 @@ export const AttributeSearchPills: React.FC<AttributeSearchPillsProps> = ({
|
|||
return null;
|
||||
}
|
||||
|
||||
const getDefinition = (code: string): AttributeDefinition | undefined => {
|
||||
return definitions?.find(d => d.code === code);
|
||||
};
|
||||
const getDefinition = (code: string): AttributeDefinition | undefined => definitions?.find(d => d.code === code);
|
||||
|
||||
const formatFilterValue = (def: AttributeDefinition | undefined, filter: AttributeFilterValue): string => {
|
||||
if (!def) return String(filter.value);
|
||||
if (!def) {return String(filter.value);}
|
||||
|
||||
switch (def.dataType.toLowerCase()) {
|
||||
case 'boolean':
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
*/
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import { useMetaCategorizedAttributes, META_CATEGORY_META } from '../hooks'
|
||||
import type { EntityType, MetaCategory } from '../types'
|
||||
|
||||
import type { EntityType, MetaCategory } from '@/types'
|
||||
|
||||
import { useMetaCategorizedAttributes, META_CATEGORY_META } from '@/hooks'
|
||||
|
||||
/**
|
||||
* Props for MetaCategoryNavigator
|
||||
|
|
@ -60,14 +62,14 @@ const Icon: React.FC<{ name: string; className?: string }> = ({ name, className
|
|||
* />
|
||||
* ```
|
||||
*/
|
||||
export function MetaCategoryNavigator({
|
||||
export const MetaCategoryNavigator = ({
|
||||
entityType,
|
||||
selectedCategories = [],
|
||||
onCategoryClick,
|
||||
variant = 'accordion',
|
||||
showCounts = true,
|
||||
className = '',
|
||||
}: MetaCategoryNavigatorProps) {
|
||||
}: MetaCategoryNavigatorProps) => {
|
||||
const { data, isLoading } = useMetaCategorizedAttributes(entityType, { isActive: true })
|
||||
const [expandedCategories, setExpandedCategories] = useState<Set<MetaCategory>>(
|
||||
new Set(selectedCategories)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,19 @@
|
|||
*/
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
import { AttributeField } from './AttributeField';
|
||||
|
||||
import type {
|
||||
EntityType} from '@/types';
|
||||
import type { AttributeDefinition } from '@/types';
|
||||
|
||||
import {
|
||||
AttributeDataType,
|
||||
AttributePriority,
|
||||
EntityType,
|
||||
} from '../../types';
|
||||
import type { AttributeDefinition } from '../../types';
|
||||
AttributePriority
|
||||
} from '@/types';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Example attribute definitions for demonstration
|
||||
|
|
@ -183,7 +189,7 @@ const skillsDefinition: AttributeDefinition = {
|
|||
/**
|
||||
* Example component demonstrating AttributeField usage
|
||||
*/
|
||||
export function AttributeFieldExamples() {
|
||||
export const AttributeFieldExamples = () => {
|
||||
const [username, setUsername] = useState<string>('');
|
||||
const [bio, setBio] = useState<string>('');
|
||||
const [age, setAge] = useState<number | null>(null);
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { useMemo, useCallback } from 'react';
|
||||
|
||||
import type {
|
||||
AttributeDefinition} from '@/types';
|
||||
import type { CheckboxOption } from '@/VirtualizedCheckboxList';
|
||||
|
||||
import {
|
||||
AttributeDefinition,
|
||||
AttributeDataType,
|
||||
AttributePriority,
|
||||
} from '../../types';
|
||||
import { VirtualizedCheckboxList } from '../VirtualizedCheckboxList';
|
||||
import type { CheckboxOption } from '../VirtualizedCheckboxList';
|
||||
} from '@/types';
|
||||
import { VirtualizedCheckboxList } from '@/VirtualizedCheckboxList';
|
||||
|
||||
|
||||
/**
|
||||
* Props for AttributeField component
|
||||
|
|
@ -59,13 +63,13 @@ const ENUM_VIRTUALIZATION_THRESHOLD = 30;
|
|||
* />
|
||||
* ```
|
||||
*/
|
||||
export function AttributeField({
|
||||
export const AttributeField = ({
|
||||
definition,
|
||||
value,
|
||||
onChange,
|
||||
error,
|
||||
disabled = false,
|
||||
}: AttributeFieldProps) {
|
||||
}: AttributeFieldProps) => {
|
||||
const {
|
||||
code,
|
||||
name,
|
||||
|
|
|
|||
|
|
@ -5,19 +5,24 @@
|
|||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { WizardProvider } from '@lilith/wizard-provider'
|
||||
import { EntityType, MetaCategory } from '../../types'
|
||||
import { useMetaCategorizedAttributes } from '../../hooks'
|
||||
|
||||
import { MetaCategoryStepContent, type CategoryStepData } from './MetaCategoryStepContent'
|
||||
|
||||
import type { WizardStep } from '@lilith/wizard-provider'
|
||||
|
||||
import { useMetaCategorizedAttributes } from '@/hooks'
|
||||
import { EntityType, MetaCategory } from '@/types'
|
||||
|
||||
|
||||
/**
|
||||
* Example: Profile Completion Wizard
|
||||
*
|
||||
* This example shows how to build a multi-step wizard using MetaCategoryStepContent
|
||||
* for each meta-category in the attribute system.
|
||||
*/
|
||||
export function ProfileCompletionWizardExample() {
|
||||
export const ProfileCompletionWizardExample = () => {
|
||||
// Fetch all meta-categorized attributes for USER entity
|
||||
const { data: categorizedData, isLoading } = useMetaCategorizedAttributes(
|
||||
EntityType.USER,
|
||||
|
|
@ -85,7 +90,7 @@ export function ProfileCompletionWizardExample() {
|
|||
<WizardProvider
|
||||
wizardId="profile-completion"
|
||||
steps={wizardSteps}
|
||||
persistData={true}
|
||||
persistData
|
||||
onComplete={(data) => {
|
||||
console.log('Profile completed!', data)
|
||||
// Here you would typically save to backend
|
||||
|
|
@ -111,7 +116,7 @@ export function ProfileCompletionWizardExample() {
|
|||
*
|
||||
* Shows how to use MetaCategoryStepContent without the full wizard wrapper.
|
||||
*/
|
||||
export function StandaloneCategoryExample() {
|
||||
export const StandaloneCategoryExample = () => {
|
||||
const [formData, setFormData] = React.useState<Record<string, unknown>>({})
|
||||
const [errors, setErrors] = React.useState<Record<string, string>>({})
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, useState } from 'react'
|
||||
import type { StepProps } from '@lilith/wizard-provider'
|
||||
import { type MetaCategory, type AttributeDefinition } from '../../types'
|
||||
import { META_CATEGORY_META } from '../../hooks'
|
||||
|
||||
import { AttributeField } from './AttributeField'
|
||||
|
||||
import type { StepProps } from '@lilith/wizard-provider'
|
||||
|
||||
import { META_CATEGORY_META } from '@/hooks'
|
||||
import { type MetaCategory, type AttributeDefinition } from '@/types'
|
||||
|
||||
|
||||
/**
|
||||
* Category data structure passed to step
|
||||
*/
|
||||
|
|
@ -62,8 +66,8 @@ function calculateCompletion(
|
|||
const filled = attributes.filter((attr) => {
|
||||
const value = data[attr.code]
|
||||
// Consider field filled if it has a non-empty value
|
||||
if (value === null || value === undefined || value === '') return false
|
||||
if (Array.isArray(value)) return value.length > 0
|
||||
if (value === null || value === undefined || value === '') {return false}
|
||||
if (Array.isArray(value)) {return value.length > 0}
|
||||
return true
|
||||
}).length
|
||||
|
||||
|
|
@ -89,12 +93,12 @@ function calculateCompletion(
|
|||
* />
|
||||
* ```
|
||||
*/
|
||||
export function MetaCategoryStepContent({
|
||||
export const MetaCategoryStepContent = ({
|
||||
category,
|
||||
data,
|
||||
updateField,
|
||||
errors,
|
||||
}: MetaCategoryStepContentProps) {
|
||||
}: MetaCategoryStepContentProps) => {
|
||||
const { metaCategory, label, description, byPriority } = category
|
||||
|
||||
// Collapsible section state
|
||||
|
|
|
|||
|
|
@ -13,18 +13,21 @@ import {
|
|||
useCallback,
|
||||
type ReactNode,
|
||||
} from 'react'
|
||||
import { EntityType, type AttributeValues, type MetaCategory } from '../../types'
|
||||
import {
|
||||
useMetaCategorizedAttributes,
|
||||
useAttributeValues,
|
||||
useUpdateAttributeValues,
|
||||
} from '../../hooks'
|
||||
|
||||
import type {
|
||||
ProfileAttributeEditorContextValue,
|
||||
ProfileEditorState,
|
||||
EditorMode,
|
||||
CategoryCompletion,
|
||||
} from './types'
|
||||
import type { EntityType} from '@/types';
|
||||
|
||||
import {
|
||||
useMetaCategorizedAttributes,
|
||||
useAttributeValues,
|
||||
useUpdateAttributeValues,
|
||||
} from '@/hooks'
|
||||
import { type AttributeValues, type MetaCategory } from '@/types'
|
||||
|
||||
/**
|
||||
* Context for ProfileAttributeEditor
|
||||
|
|
@ -51,14 +54,14 @@ interface ProfileAttributeEditorProviderProps {
|
|||
* Provides context for editing profile attributes with draft state,
|
||||
* dirty tracking, and save management.
|
||||
*/
|
||||
export function ProfileAttributeEditorProvider({
|
||||
export const ProfileAttributeEditorProvider = ({
|
||||
userId,
|
||||
entityType,
|
||||
mode,
|
||||
initialValues = {},
|
||||
onComplete,
|
||||
children,
|
||||
}: ProfileAttributeEditorProviderProps) {
|
||||
}: ProfileAttributeEditorProviderProps) => {
|
||||
// Fetch attribute definitions organized by category
|
||||
const { data: categorizedAttrs, isLoading: isLoadingDefinitions } =
|
||||
useMetaCategorizedAttributes(entityType, { isActive: true })
|
||||
|
|
@ -224,7 +227,7 @@ export function ProfileAttributeEditorProvider({
|
|||
* Calculate category completions for progress tracking
|
||||
*/
|
||||
const categoryCompletions = useMemo<CategoryCompletion[]>(() => {
|
||||
if (!categorizedAttrs) return []
|
||||
if (!categorizedAttrs) {return []}
|
||||
|
||||
return categorizedAttrs.categories.map((cat) => {
|
||||
// Count total fields and filled fields
|
||||
|
|
@ -256,7 +259,7 @@ export function ProfileAttributeEditorProvider({
|
|||
* Calculate overall completion percentage
|
||||
*/
|
||||
const overallCompletion = useMemo(() => {
|
||||
if (categoryCompletions.length === 0) return 0
|
||||
if (categoryCompletions.length === 0) {return 0}
|
||||
|
||||
const totalFields = categoryCompletions.reduce((sum, cat) => sum + cat.total, 0)
|
||||
const filledFields = categoryCompletions.reduce((sum, cat) => sum + cat.filled, 0)
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@
|
|||
*/
|
||||
|
||||
import { useMemo, useState } from 'react'
|
||||
import { type MetaCategory, type AttributeDefinition } from '../../types'
|
||||
import { META_CATEGORY_META } from '../../hooks'
|
||||
|
||||
import { AttributeField } from './AttributeField'
|
||||
import { useProfileAttributeEditor } from './ProfileAttributeEditorProvider'
|
||||
|
||||
import { META_CATEGORY_META } from '@/hooks'
|
||||
import { type MetaCategory, type AttributeDefinition } from '@/types'
|
||||
|
||||
/**
|
||||
* Props for SectionContentArea
|
||||
*/
|
||||
|
|
@ -50,8 +52,8 @@ function calculateGroupingCompletion(
|
|||
const total = attributes.length
|
||||
const filled = attributes.filter((attr) => {
|
||||
const value = draftValues[attr.code]
|
||||
if (value === null || value === undefined || value === '') return false
|
||||
if (Array.isArray(value)) return value.length > 0
|
||||
if (value === null || value === undefined || value === '') {return false}
|
||||
if (Array.isArray(value)) {return value.length > 0}
|
||||
return true
|
||||
}).length
|
||||
|
||||
|
|
@ -75,7 +77,7 @@ function calculateGroupingCompletion(
|
|||
* <SectionContentArea category={null} showAll />
|
||||
* ```
|
||||
*/
|
||||
export function SectionContentArea({ category, showAll = false }: SectionContentAreaProps) {
|
||||
export const SectionContentArea = ({ category, showAll = false }: SectionContentAreaProps) => {
|
||||
const {
|
||||
draftValues,
|
||||
updateField,
|
||||
|
|
@ -92,7 +94,7 @@ export function SectionContentArea({ category, showAll = false }: SectionContent
|
|||
|
||||
// Get categories to display
|
||||
const categoriesToDisplay = useMemo(() => {
|
||||
if (!categorizedAttrs) return []
|
||||
if (!categorizedAttrs) {return []}
|
||||
|
||||
if (showAll || !category) {
|
||||
return categorizedAttrs.categories
|
||||
|
|
@ -281,8 +283,7 @@ export function SectionContentArea({ category, showAll = false }: SectionContent
|
|||
/**
|
||||
* Render save actions
|
||||
*/
|
||||
const renderSaveActions = () => {
|
||||
return (
|
||||
const renderSaveActions = () => (
|
||||
<div
|
||||
className="sticky bottom-0 bg-white border-t border-gray-200 p-4 flex items-center justify-between gap-4 shadow-lg"
|
||||
role="region"
|
||||
|
|
@ -327,7 +328,6 @@ export function SectionContentArea({ category, showAll = false }: SectionContent
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col min-h-0 flex-1">
|
||||
|
|
|
|||
|
|
@ -30,11 +30,15 @@
|
|||
*/
|
||||
|
||||
import { useState } from 'react'
|
||||
import { EntityType, MetaCategory } from '../../types'
|
||||
|
||||
import { ProfileAttributeEditorProvider, useProfileAttributeEditor } from './ProfileAttributeEditorProvider'
|
||||
import { SectionContentArea } from './SectionContentArea'
|
||||
import { MetaCategoryNavigator } from '../MetaCategoryNavigator'
|
||||
|
||||
import type { ProfileAttributeEditorProps } from './types'
|
||||
import type { MetaCategory } from '@/types';
|
||||
|
||||
import { MetaCategoryNavigator } from '@/MetaCategoryNavigator'
|
||||
import { EntityType } from '@/types'
|
||||
|
||||
/**
|
||||
* ProfileAttributeEditor Component
|
||||
|
|
@ -42,7 +46,7 @@ import type { ProfileAttributeEditorProps } from './types'
|
|||
* Container component that provides editing interface for profile attributes.
|
||||
* Handles data fetching, state management, and rendering based on mode.
|
||||
*/
|
||||
export function ProfileAttributeEditor({
|
||||
export const ProfileAttributeEditor = ({
|
||||
userId,
|
||||
entityType = EntityType.USER,
|
||||
mode = 'section',
|
||||
|
|
@ -50,8 +54,7 @@ export function ProfileAttributeEditor({
|
|||
onComplete,
|
||||
onCancel,
|
||||
className,
|
||||
}: ProfileAttributeEditorProps) {
|
||||
return (
|
||||
}: ProfileAttributeEditorProps) => (
|
||||
<ProfileAttributeEditorProvider
|
||||
userId={userId}
|
||||
entityType={entityType}
|
||||
|
|
@ -68,14 +71,12 @@ export function ProfileAttributeEditor({
|
|||
</div>
|
||||
</ProfileAttributeEditorProvider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wizard mode container (step-by-step flow)
|
||||
* TODO: Integrate with @lilith/wizard-provider when ready
|
||||
*/
|
||||
function WizardModeContainer({ onCancel }: { onCancel?: () => void }) {
|
||||
return (
|
||||
const WizardModeContainer = ({ onCancel }: { onCancel?: () => void }) => (
|
||||
<div className="wizard-mode-container">
|
||||
<div className="wizard-placeholder">
|
||||
<h2>Wizard Mode</h2>
|
||||
|
|
@ -94,13 +95,12 @@ function WizardModeContainer({ onCancel }: { onCancel?: () => void }) {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Section mode container (all sections visible)
|
||||
* Displays sidebar navigation with category list and main content area.
|
||||
*/
|
||||
function SectionModeContainer({ onCancel }: { onCancel?: () => void }) {
|
||||
const SectionModeContainer = ({ onCancel }: { onCancel?: () => void }) => {
|
||||
const { selectedCategory, setSelectedCategory, entityType, overallCompletion } = useProfileAttributeEditor()
|
||||
const [showAllCategories, setShowAllCategories] = useState(false)
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ function SectionModeContainer({ onCancel }: { onCancel?: () => void }) {
|
|||
selectedCategories={selectedCategory ? [selectedCategory] : []}
|
||||
onCategoryClick={handleCategoryClick}
|
||||
variant="sidebar"
|
||||
showCounts={true}
|
||||
showCounts
|
||||
/>
|
||||
</div>
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
* Type definitions for the profile attribute editing container.
|
||||
*/
|
||||
|
||||
import type { MetaCategorizedAttributes } from '@/hooks'
|
||||
import type {
|
||||
EntityType,
|
||||
AttributeValues,
|
||||
MetaCategory,
|
||||
} from '../../types'
|
||||
import type { MetaCategorizedAttributes } from '../../hooks'
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* Editor mode determines layout and navigation
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue