import { CUSTOM_COMPONENTS, HTML_ELEMENTS } from '@/components/codeLayout/data/componentDefinitions' import generatedComponentProps from './generated/componentProps.json' import { DESIGNER_DATA_COMPONENT_NAMES, getDesignerCollectionProperty, isDesignerOptionComponent, isDesignerTabularComponent, type DesignerComponentDefinition, type DesignerNodeKind, } from './types' export { DESIGNER_DATA_COMPONENT_NAMES } interface GeneratedProperty { name: string tsType: string type: 'string' | 'boolean' | 'number' | 'function' | 'object' | 'array' | 'select' required: boolean options?: string[] defaultValue?: unknown } interface GeneratedComponentMetadata { interfaceName: string sourceFile: string properties: GeneratedProperty[] } const generatedMetadata = generatedComponentProps as Record const CONTAINER_NAMES = new Set(['div', 'Card', 'FormContainer', 'Table', 'Tabs', 'Timeline']) const getToolboxGroup = (name: string): DesignerComponentDefinition['toolboxGroup'] => name === 'Table' ? 'layout' : DESIGNER_DATA_COMPONENT_NAMES.has(name) ? 'data' : 'ui' /** Components intentionally kept out of the toolbox. */ const EXCLUDED_COMPONENTS = new Set([ 'Alert', 'Breadcrumb', 'Dialog', 'Drawer', 'FormContainer', 'FormItem', 'InputGroup', 'MenuItem', 'Segment', 'Steps', ]) interface PropertyOverride { /** Seed value that makes a freshly dropped component render something useful. */ value?: unknown /** Corrects an editor type the metadata generator could not infer. */ type?: GeneratedProperty['type'] options?: string[] } const PROPERTY_OVERRIDES: Record> = { // `Exclude | 'square'` collapses to a single option. Avatar: { shape: { options: ['round', 'circle', 'square'], value: 'circle' } }, Chips: { value: { value: ['Deneme', 'Deneme2'] } }, // ImageViewer renders nothing without images, and its clickable gallery only // appears when `children` is set — so a dropped component would be invisible. ImageViewer: { images: { value: [ { src: 'https://picsum.photos/id/1015/1200/800', alt: 'Görsel 1', caption: 'Görsel 1' }, { src: 'https://picsum.photos/id/1025/1200/800', alt: 'Görsel 2', caption: 'Görsel 2' }, ], }, children: { value: 'Galeri' }, }, Marquee: { gradient: { value: false }, speed: { value: 100 } }, } const HTML_UI_DUPLICATES = new Set(['input', 'button', 'textarea', 'select', 'checkbox']) const UI_PROPERTY_ALIASES: Record> = { Select: { clearable: 'isClearable', disabled: 'isDisabled', multiple: 'isMulti', onSearch: 'onInputChange', searchable: 'isSearchable', }, } export const DESIGNER_PRIMARY_EVENTS: Record = { AutoComplete: ['onInputChange', 'onSelect'], Button: ['onClick'], Checkbox: ['onChange'], Dropdown: ['onSelect'], Input: ['onChange'], Menu: ['onSelect'], Pagination: ['onChange'], 'Radio.Group': ['onChange'], Select: ['onChange'], Tabs: ['onChange'], } const DESIGNER_EVENT_SNIPPETS: Record> = { Button: { onClick: `// Butona tıklandığında çalışır. console.log('Button clicked', event)`, }, Checkbox: { onChange: `// Checkbox bileşeninin yeni checked değeri. const checked = event.checked console.log('Checked:', checked)`, }, Input: { onChange: `// Input alanının güncel metin değeri. const value = event.target.value console.log('Value:', value)`, }, Menu: { onSelect: `// Seçilen menü öğesinin value değeridir. console.log('Menu value:', event)`, }, Dropdown: { onSelect: `// Seçilen dropdown öğesinin value değeridir. console.log('Dropdown value:', event)`, }, Pagination: { onChange: `// Seçilen sayfa numarasıdır. console.log('Page:', event)`, }, 'Radio.Group': { onChange: `// Seçilen radio öğesinin value değeridir. console.log('Radio value:', event)`, }, Select: { onChange: `// Seçilen option nesnesi; temizlendiğinde null olur. const selectedValue = event?.value ?? null const selectedLabel = event?.label ?? '' console.log({ selectedValue, selectedLabel })`, }, } export const getDesignerEventSnippet = (componentName: string, eventName: string) => DESIGNER_EVENT_SNIPPETS[componentName]?.[eventName] || `// ${componentName}.${eventName}(event)\nconsole.log(event)` const COLLECTION_ITEMS_PROPERTY = { name: 'items', type: 'array' as const, value: [], category: 'properties' as const, description: 'Statik veya endpoint kaynaklı veri koleksiyonu', tsType: 'unknown[]', options: undefined, required: false, } const GRID_COMPACT_PROPERTY = { name: 'compact', type: 'boolean' as const, value: false, category: 'properties' as const, description: 'Satır ve hücre boşluklarını azaltır', tsType: 'boolean', options: undefined, required: false, } const GRID_TABLE_BEHAVIOR_PROPERTIES = [ { name: 'hoverable', type: 'boolean' as const, value: true, category: 'properties' as const, description: 'Satırların üzerine gelindiğinde vurgu rengi gösterir', tsType: 'boolean', options: undefined, required: false, }, { name: 'overflow', type: 'boolean' as const, value: true, category: 'properties' as const, description: 'Geniş tablolarda yatay kaydırmayı etkinleştirir', tsType: 'boolean', options: undefined, required: false, }, { name: 'borderlessRow', type: 'boolean' as const, value: false, category: 'properties' as const, description: 'Veri satırları arasındaki kenarlıkları kaldırır', tsType: 'boolean', options: undefined, required: false, }, ] const TABLE_DIMENSION_PROPERTIES = [ { name: 'rowCount', type: 'number' as const, value: 2, category: 'properties' as const, description: 'Table içindeki statik satır sayısı', tsType: 'number', options: undefined, required: false, }, { name: 'columnCount', type: 'number' as const, value: 3, category: 'properties' as const, description: 'Her satırdaki statik hücre sayısı', tsType: 'number', options: undefined, required: false, }, ] const SELECT_OPTIONS_PROPERTY = { name: 'options', type: 'array' as const, value: [ { label: 'Seçenek 1', value: '1' }, { label: 'Seçenek 2', value: '2' }, ], category: 'properties' as const, description: 'Select items; statik olarak veya endpoint koleksiyonundan beslenebilir', tsType: 'Array<{ label: string; value: unknown }>', options: undefined, required: false, } const MENU_ITEMS_PROPERTY = { ...SELECT_OPTIONS_PROPERTY, name: 'items', description: 'Menu items; statik olarak veya endpoint koleksiyonundan beslenebilir', } const TABS_ITEMS_PROPERTY = { ...SELECT_OPTIONS_PROPERTY, name: 'items', value: [ { label: 'Sekme 1', value: '1', content: 'Sekme 1 içeriği' }, { label: 'Sekme 2', value: '2', content: 'Sekme 2 içeriği' }, ], description: 'Sekmeler; statik olarak veya endpoint koleksiyonundan beslenebilir. content alanı sekme içeriğini yazar.', tsType: 'Array<{ label: string; value: unknown; content?: string }>', } /** Seed definition for a data component's collection property. */ const getCollectionPropertyDefinition = (name: string, collectionProperty: string) => { if (isDesignerTabularComponent(name)) return COLLECTION_ITEMS_PROPERTY if (name === 'Tabs') return TABS_ITEMS_PROPERTY return collectionProperty === 'items' ? MENU_ITEMS_PROPERTY : SELECT_OPTIONS_PROPERTY } const normalizeDefinition = ( definition: (typeof CUSTOM_COMPONENTS)[number], kind: DesignerNodeKind, toolboxGroup: DesignerComponentDefinition['toolboxGroup'], ): DesignerComponentDefinition => { const sourceProperties = definition.name === 'Grid' ? definition.properties.filter((property) => property.name !== 'children') : definition.properties let properties = definition.name === 'Grid' && !sourceProperties.some((property) => property.name === 'items') ? [COLLECTION_ITEMS_PROPERTY, ...sourceProperties] : sourceProperties if (definition.name === 'Grid' && !properties.some((property) => property.name === 'compact')) { properties = [GRID_COMPACT_PROPERTY, ...properties] } if (definition.name === 'Grid') { properties = [ ...GRID_TABLE_BEHAVIOR_PROPERTIES.filter( (candidate) => !properties.some((property) => property.name === candidate.name), ), ...properties, ] } if (definition.name === 'Table') { properties = [ ...TABLE_DIMENSION_PROPERTIES.filter( (candidate) => !properties.some((property) => property.name === candidate.name), ), ...properties.filter((property) => !['columns', 'data', 'items'].includes(property.name)), ] } return { ...definition, properties, kind, toolboxGroup, acceptsChildren: CONTAINER_NAMES.has(definition.name), } } const fallbackValue = (type: GeneratedProperty['type']) => { if (type === 'boolean') return false if (type === 'number') return 0 if (type === 'array') return [] if (type === 'object') return {} return '' } const inferUiCategory = (name: string) => { if ( new Set([ 'AutoComplete', 'Checkbox', 'Chips', 'ColorPicker', 'DatePicker', 'Input', 'Knob', 'Radio', 'Rate', 'Select', 'Slider', 'Switcher', 'TimeInput', 'Upload', ]).has(name) ) return 'form' if (new Set(['Alert', 'Notification', 'Progress', 'Skeleton', 'Spinner']).has(name)) return 'feedback' if (new Set(['Breadcrumb', 'Dropdown', 'Menu', 'Pagination', 'Steps', 'Tabs']).has(name)) return 'navigation' return 'display' } const generatedPropertiesToDesigner = ( generated: GeneratedComponentMetadata, existingProperties = new Map(), componentName = '', ) => generated.properties.map((property) => { const existing = existingProperties.get(property.name) const override = PROPERTY_OVERRIDES[componentName]?.[property.name] const type = override?.type || property.type return { name: property.name, type, value: override && 'value' in override ? override.value : property.defaultValue !== undefined ? property.defaultValue : existing?.value !== undefined ? existing.value : fallbackValue(type), options: override?.type ? override.options : override?.options || property.options || existing?.options, description: property.tsType, category: property.type === 'function' ? ('events' as const) : existing?.category || (property.name === 'className' || property.name === 'style' ? ('styling' as const) : ('properties' as const)), tsType: property.tsType, required: property.required, } }) const normalizeUiDefinition = ( definition: (typeof CUSTOM_COMPONENTS)[number], ): DesignerComponentDefinition => { const generated = generatedMetadata[definition.name] if (!generated) { return normalizeDefinition(definition, 'ui', getToolboxGroup(definition.name)) } const propertyAliases = UI_PROPERTY_ALIASES[definition.name] || {} const curatedProperties = definition.properties .filter( (property) => (definition.name !== 'Select' || property.name !== 'items') && (definition.name !== 'Menu' || property.name !== 'variant') && (definition.name !== 'Table' || !['columns', 'data'].includes(property.name)), ) .map((property) => ({ ...property, name: propertyAliases[property.name] || property.name, })) const existingProperties = new Map(curatedProperties.map((property) => [property.name, property])) const generatedProperties = generatedPropertiesToDesigner( generated, existingProperties, definition.name, ) // Every data component must expose its collection property so the data panel // can drive it from static values or an endpoint. const collectionProperty = getDesignerCollectionProperty(definition.name) if ( isDesignerOptionComponent(definition.name) && !generatedProperties.some((property) => property.name === collectionProperty) ) { generatedProperties.unshift( getCollectionPropertyDefinition(definition.name, collectionProperty), ) } if ( isDesignerTabularComponent(definition.name) && !generatedProperties.some((property) => property.name === collectionProperty) ) { generatedProperties.unshift(COLLECTION_ITEMS_PROPERTY) } if (definition.name === 'Table') { generatedProperties.unshift( ...TABLE_DIMENSION_PROPERTIES.filter( (candidate) => !generatedProperties.some((property) => property.name === candidate.name), ), ) } const generatedPropertyNames = new Set(generatedProperties.map((property) => property.name)) const properties = [ ...generatedProperties, // Keep explicitly curated inherited React props such as Input.value and placeholder. ...curatedProperties.filter((property) => !generatedPropertyNames.has(property.name)), ].filter( (property) => !(definition.name === 'Checkbox' && property.name === 'defaultChecked') && !(definition.name === 'Menu' && property.name === 'variant'), ) const primaryEvents = DESIGNER_PRIMARY_EVENTS[definition.name] return { ...definition, kind: 'ui', toolboxGroup: getToolboxGroup(definition.name), acceptsChildren: CONTAINER_NAMES.has(definition.name), sourceFile: generated.sourceFile, interfaceName: generated.interfaceName, properties: primaryEvents ? properties.filter( (property) => (property.category !== 'events' && property.type !== 'function') || primaryEvents.includes(property.name), ) : properties, } } const platformDefinition = ( name: string, sourceName: string, description: string, height = '520px', ): DesignerComponentDefinition => ({ name, sourceName, icon: 'PanelsTopLeft', category: 'platform', kind: 'platform', toolboxGroup: 'platform', description, properties: [ { name: 'listFormCode', type: 'string', value: '', category: 'properties', required: true }, { name: 'height', type: 'string', value: height, category: 'styling' }, ], hooks: [], }) export const PLATFORM_COMPONENTS: DesignerComponentDefinition[] = [ platformDefinition( 'ListView', 'List', 'Tüm etkin platform görünümlerini /admin/list/:listFormCode ile aynı şekilde yönetir', ), platformDefinition('DataGridView', 'Grid', 'Sozsoft dinamik veri grid görünümü'), platformDefinition('TreeView', 'Tree', 'Sozsoft dinamik ağaç görünümü'), platformDefinition('GanttView', 'GanttView', 'Sozsoft dinamik Gantt görünümü'), platformDefinition('TodoBoard', 'TodoBoard', 'Sozsoft dinamik iş panosu'), platformDefinition('CardView', 'CardView', 'Sozsoft dinamik kart görünümü'), platformDefinition('SchedulerView', 'SchedulerView', 'Sozsoft dinamik planlayıcı görünümü'), platformDefinition('PivotView', 'Pivot', 'Sozsoft dinamik pivot görünümü'), platformDefinition('ChartView', 'Chart', 'Sozsoft dinamik grafik görünümü', '420px'), ] export const DESIGNER_EXTRAS: DesignerComponentDefinition[] = [ { name: 'PageContainer', icon: 'PanelTop', category: 'layout', kind: 'layout', toolboxGroup: 'layout', description: 'Intranet içeriği için ortalanmış sayfa alanı', acceptsChildren: true, properties: [ { name: 'maxWidth', type: 'string', value: '1280px', category: 'styling' }, { name: 'padding', type: 'number', value: 24, category: 'styling' }, { name: 'gap', type: 'number', value: 16, category: 'styling' }, { name: 'className', type: 'string', value: '', category: 'styling' }, ], hooks: [], }, { name: 'TwoColumns', icon: 'Columns2', category: 'layout', kind: 'layout', toolboxGroup: 'layout', description: 'Sayfayı iki eşit kolona böler', acceptsChildren: true, properties: [ { name: 'gap', type: 'number', value: 16, category: 'styling' }, { name: 'className', type: 'string', value: '', category: 'styling' }, ], hooks: [], }, { name: 'SidebarContent', icon: 'PanelLeft', category: 'layout', kind: 'layout', toolboxGroup: 'layout', description: 'Dar sidebar ve geniş içerik alanı', acceptsChildren: true, properties: [ { name: 'sidebarWidth', type: 'string', value: '280px', category: 'styling' }, { name: 'gap', type: 'number', value: 16, category: 'styling' }, { name: 'className', type: 'string', value: '', category: 'styling' }, ], hooks: [], }, { name: 'HeaderContent', icon: 'PanelTop', category: 'layout', kind: 'layout', toolboxGroup: 'layout', description: 'Üst alan ve alt içerik için dikey yerleşim', acceptsChildren: true, properties: [ { name: 'gap', type: 'number', value: 16, category: 'styling' }, { name: 'className', type: 'string', value: '', category: 'styling' }, ], hooks: [], }, { name: 'FlexRow', icon: 'Columns3', category: 'layout', kind: 'layout', toolboxGroup: 'layout', description: 'Yatay ve responsive yerleşim', acceptsChildren: true, properties: [ { name: 'gap', type: 'number', value: 16, category: 'styling' }, { name: 'wrap', type: 'boolean', value: true, category: 'styling' }, { name: 'align', type: 'select', value: 'stretch', options: ['stretch', 'start', 'center', 'end'], category: 'styling', }, { name: 'className', type: 'string', value: '', category: 'styling' }, ], hooks: [], }, { name: 'Spacer', icon: 'Space', category: 'layout', kind: 'layout', toolboxGroup: 'ui', description: 'Bileşenler arasında boşluk', properties: [{ name: 'height', type: 'number', value: 24, category: 'styling' }], hooks: [], }, ] export const getDesignerCatalog = (customNames: string[] = []): DesignerComponentDefinition[] => { const html = HTML_ELEMENTS.filter((definition) => !HTML_UI_DUPLICATES.has(definition.name)).map( (definition) => normalizeDefinition(definition, 'html', 'html'), ) const ui = CUSTOM_COMPONENTS.filter( (definition) => !EXCLUDED_COMPONENTS.has(definition.name), ).map(normalizeUiDefinition) const definedUiNames = new Set(CUSTOM_COMPONENTS.map((definition) => definition.name)) const generatedOnlyUi: DesignerComponentDefinition[] = Object.entries(generatedMetadata) .filter(([name]) => !definedUiNames.has(name) && !EXCLUDED_COMPONENTS.has(name)) .map(([name, metadata]) => { const properties = generatedPropertiesToDesigner(metadata, undefined, name) const collectionProperty = getDesignerCollectionProperty(name) if ( DESIGNER_DATA_COMPONENT_NAMES.has(name) && !properties.some((property) => property.name === collectionProperty) ) { properties.unshift(getCollectionPropertyDefinition(name, collectionProperty)) } return { name, icon: 'Blocks', category: inferUiCategory(name), kind: 'ui' as const, toolboxGroup: getToolboxGroup(name), description: `${metadata.interfaceName} sözleşmesini kullanan Sozsoft UI component’i`, sourceFile: metadata.sourceFile, interfaceName: metadata.interfaceName, acceptsChildren: CONTAINER_NAMES.has(name), properties, hooks: [], } }) const custom: DesignerComponentDefinition[] = customNames.map((name) => ({ name, icon: 'Blocks', category: 'custom', kind: 'custom', toolboxGroup: 'custom', description: 'Veritabanındaki yeniden kullanılabilir bileşen', acceptsChildren: true, properties: [{ name: 'className', type: 'string', value: '', category: 'styling' }], hooks: [], })) return [...DESIGNER_EXTRAS, ...PLATFORM_COMPONENTS, ...html, ...ui, ...generatedOnlyUi, ...custom] }